summaryrefslogtreecommitdiff
path: root/share/shares.c
diff options
context:
space:
mode:
Diffstat (limited to 'share/shares.c')
-rw-r--r--share/shares.c1023
1 files changed, 1023 insertions, 0 deletions
diff --git a/share/shares.c b/share/shares.c
new file mode 100644
index 0000000..cd609f3
--- /dev/null
+++ b/share/shares.c
@@ -0,0 +1,1023 @@
+#include <config.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <glib/gi18n-lib.h>
+#include "shares.h"
+
+#undef DEBUG_SHARES
+#ifdef DEBUG_SHARES
+# define NET_USERSHARE_ARGV0 "debug-net-usershare"
+#else
+# define NET_USERSHARE_ARGV0 "net"
+#endif
+
+static GHashTable *path_share_info_hash;
+static GHashTable *share_name_share_info_hash;
+
+#define NUM_CALLS_BETWEEN_TIMESTAMP_UPDATES 100
+#define TIMESTAMP_THRESHOLD 10 /* seconds */
+static int refresh_timestamp_update_counter;
+static time_t refresh_timestamp;
+
+#define KEY_PATH "path"
+#define KEY_COMMENT "comment"
+#define KEY_ACL "usershare_acl"
+#define KEY_GUEST_OK "guest_ok"
+#define GROUP_ALLOW_GUESTS "global"
+#define KEY_ALLOW_GUESTS "usershare allow guests"
+
+/* Debugging flags */
+static gboolean throw_error_on_refresh;
+static gboolean throw_error_on_add;
+static gboolean throw_error_on_modify;
+static gboolean throw_error_on_remove;
+
+
+
+/* Interface to "net usershare" */
+
+static gboolean
+net_usershare_run (int argc, char **argv, GKeyFile **ret_key_file, GError **error)
+{
+ int real_argc;
+ int i;
+ char **real_argv;
+ gboolean retval;
+ char *stdout_contents;
+ char *stderr_contents;
+ int exit_status;
+ int exit_code;
+ GKeyFile *key_file;
+ GError *real_error;
+
+ g_assert (argc > 0);
+ g_assert (argv != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (ret_key_file)
+ *ret_key_file = NULL;
+
+ /* Build command line */
+
+ real_argc = 2 + argc + 1; /* "net" "usershare" [argv] NULL */
+ real_argv = g_new (char *, real_argc);
+
+ real_argv[0] = NET_USERSHARE_ARGV0;
+ real_argv[1] = "usershare";
+
+ for (i = 0; i < argc; i++) {
+ g_assert (argv[i] != NULL);
+ real_argv[i + 2] = argv[i];
+ }
+
+ real_argv[real_argc - 1] = NULL;
+
+ /* Launch */
+
+ stdout_contents = NULL;
+ stderr_contents = NULL;
+ /*
+ {
+ char **p;
+
+ g_message ("------------------------------------------");
+
+ for (p = real_argv; *p; p++)
+ g_message ("spawn arg \"%s\"", *p);
+
+ g_message ("end of spawn args; SPAWNING\n");
+ }
+ */
+ real_error = NULL;
+ retval = g_spawn_sync (NULL, /* cwd */
+ real_argv,
+ NULL, /* envp */
+ G_SPAWN_SEARCH_PATH,
+ NULL, /* GSpawnChildSetupFunc */
+ NULL, /* user_data */
+ &stdout_contents,
+ &stderr_contents,
+ &exit_status,
+ &real_error);
+
+ /* g_message ("returned from spawn: %s: %s", retval ? "SUCCESS" : "FAIL", retval ? "" : real_error->message); */
+
+ if (!retval) {
+ g_propagate_error (error, real_error);
+ goto out;
+ }
+
+ if (!WIFEXITED (exit_status)) {
+ g_message ("WIFEXITED(%d) was false!", exit_status);
+ retval = FALSE;
+
+ if (WIFSIGNALED (exit_status)) {
+ int signal_num;
+
+ signal_num = WTERMSIG (exit_status);
+ g_message ("Child got signal %d", signal_num);
+
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("%s %s %s returned with signal %d"),
+ real_argv[0],
+ real_argv[1],
+ real_argv[2],
+ signal_num);
+ } else
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("%s %s %s failed for an unknown reason"),
+ real_argv[0],
+ real_argv[1],
+ real_argv[2]);
+
+ goto out;
+ }
+
+ exit_code = WEXITSTATUS (exit_status);
+
+ /* g_message ("exit code %d", exit_code); */
+ if (exit_code != 0) {
+ char *str;
+ char *message;
+
+ /* stderr_contents is in the system locale encoding, not UTF-8 */
+
+ str = g_locale_to_utf8 (stderr_contents, -1, NULL, NULL, NULL);
+
+ if (str && str[0])
+ message = g_strdup_printf (_("'net usershare' returned error %d: %s"), exit_code, str);
+ else
+ message = g_strdup_printf (_("'net usershare' returned error %d"), exit_code);
+
+ g_free (str);
+
+ g_set_error (error,
+ G_SPAWN_ERROR,
+ G_SPAWN_ERROR_FAILED,
+ "%s",
+ message);
+
+ g_free (message);
+
+ retval = FALSE;
+ goto out;
+ }
+
+ if (ret_key_file) {
+ /* g_message ("caller wants GKeyFile"); */
+
+ *ret_key_file = NULL;
+
+ /* FIXME: [email protected] says the output of "net usershare" is nearly always
+ * in UTF-8, but that it can be configured in the master smb.conf. We assume
+ * UTF-8 for now.
+ */
+
+ if (!g_utf8_validate (stdout_contents, -1, NULL)) {
+ g_message ("stdout of net usershare was not in valid UTF-8");
+ g_set_error (error,
+ G_SPAWN_ERROR,
+ G_SPAWN_ERROR_FAILED,
+ _("the output of 'net usershare' is not in valid UTF-8 encoding"));
+ retval = FALSE;
+ goto out;
+ }
+
+ key_file = g_key_file_new ();
+
+ real_error = NULL;
+ if (!g_key_file_load_from_data (key_file, stdout_contents, -1, 0, &real_error)) {
+ g_message ("Error when parsing key file {\n%s\n}: %s", stdout_contents, real_error->message);
+ g_propagate_error (error, real_error);
+ g_key_file_free (key_file);
+ retval = FALSE;
+ goto out;
+ }
+
+ retval = TRUE;
+ *ret_key_file = key_file;
+ } else
+ retval = TRUE;
+
+ /* g_message ("success from calling net usershare and parsing its output"); */
+
+ out:
+ g_free (real_argv);
+ g_free (stdout_contents);
+ g_free (stderr_contents);
+
+ /* g_message ("------------------------------------------"); */
+
+ return retval;
+}
+
+
+
+/* Internals */
+
+static void
+ensure_hashes (void)
+{
+ if (path_share_info_hash == NULL) {
+ g_assert (share_name_share_info_hash == NULL);
+
+ path_share_info_hash = g_hash_table_new (g_str_hash, g_str_equal);
+ share_name_share_info_hash = g_hash_table_new (g_str_hash, g_str_equal);
+ } else
+ g_assert (share_name_share_info_hash != NULL);
+}
+
+static ShareInfo *
+lookup_share_by_path (const char *path)
+{
+ ensure_hashes ();
+ return g_hash_table_lookup (path_share_info_hash, path);
+}
+
+static ShareInfo *
+lookup_share_by_share_name (const char *share_name)
+{
+ ensure_hashes ();
+ return g_hash_table_lookup (share_name_share_info_hash, share_name);
+}
+
+static void
+add_share_info_to_hashes (ShareInfo *info)
+{
+ ensure_hashes ();
+ g_hash_table_insert (path_share_info_hash, info->path, info);
+ g_hash_table_insert (share_name_share_info_hash, info->share_name, info);
+}
+
+static void
+remove_share_info_from_hashes (ShareInfo *info)
+{
+ ensure_hashes ();
+ g_hash_table_remove (path_share_info_hash, info->path);
+ g_hash_table_remove (share_name_share_info_hash, info->share_name);
+}
+
+static gboolean
+remove_from_path_hash_cb (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ ShareInfo *info;
+
+ info = value;
+ shares_free_share_info (info);
+
+ return TRUE;
+}
+
+static gboolean
+remove_from_share_name_hash_cb (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ /* The ShareInfo was already freed in remove_from_path_hash_cb() */
+ return TRUE;
+}
+
+static void
+free_all_shares (void)
+{
+ ensure_hashes ();
+ g_hash_table_foreach_remove (path_share_info_hash, remove_from_path_hash_cb, NULL);
+ g_hash_table_foreach_remove (share_name_share_info_hash, remove_from_share_name_hash_cb, NULL);
+}
+
+static char *
+get_string_from_key_file (GKeyFile *key_file, const char *group, const char *key)
+{
+ GError *error;
+ char *str;
+
+ error = NULL;
+ str = NULL;
+
+ if (g_key_file_has_key (key_file, group, key, &error)) {
+ str = g_key_file_get_string (key_file, group, key, &error);
+ if (!str) {
+ g_assert (!g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)
+ && !g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND));
+
+ g_error_free (error);
+ }
+ } else {
+ g_assert (!g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND));
+ g_error_free (error);
+ }
+
+ return str;
+}
+
+static void
+add_key_group_to_hashes (GKeyFile *key_file, const char *group)
+{
+ char *path;
+ char *comment;
+ char *acl;
+ gboolean is_writable;
+ char *guest_ok_str;
+ gboolean guest_ok;
+ ShareInfo *info;
+ ShareInfo *old_info;
+
+ /* Remove the old share based on the name */
+
+ old_info = lookup_share_by_share_name (group);
+ if (old_info) {
+ remove_share_info_from_hashes (old_info);
+ shares_free_share_info (old_info);
+ }
+
+ /* Start parsing, and remove the old share based on the path */
+
+ path = get_string_from_key_file (key_file, group, KEY_PATH);
+ if (!path) {
+ g_message ("group '%s' doesn't have a '%s' key! Ignoring group.", group, KEY_PATH);
+ return;
+ }
+
+ old_info = lookup_share_by_path (path);
+ if (old_info) {
+ remove_share_info_from_hashes (old_info);
+ shares_free_share_info (old_info);
+ }
+
+ /* Finish parsing */
+
+ comment = get_string_from_key_file (key_file, group, KEY_COMMENT);
+
+ acl = get_string_from_key_file (key_file, group, KEY_ACL);
+ if (acl) {
+ if (strstr (acl, "Everyone:R"))
+ is_writable = FALSE;
+ else if (strstr (acl, "Everyone:F"))
+ is_writable = TRUE;
+ else {
+ g_message ("unknown format for key '%s/%s' as it contains '%s'. Assuming that the share is read-only",
+ group, KEY_ACL, acl);
+ is_writable = FALSE;
+ }
+
+ g_free (acl);
+ } else {
+ g_message ("group '%s' doesn't have a '%s' key! Assuming that the share is read-only.", group, KEY_ACL);
+ is_writable = FALSE;
+ }
+
+ guest_ok_str = get_string_from_key_file (key_file, group, KEY_GUEST_OK);
+ if (guest_ok_str) {
+ if (strcmp (guest_ok_str, "n") == 0)
+ guest_ok = FALSE;
+ else if (strcmp (guest_ok_str, "y") == 0)
+ guest_ok = TRUE;
+ else {
+ g_message ("unknown format for key '%s/%s' as it contains '%s'. Assuming that the share is not guest accessible.",
+ group, KEY_GUEST_OK, guest_ok_str);
+ guest_ok = FALSE;
+ }
+
+ g_free (guest_ok_str);
+ } else {
+ g_message ("group '%s' doesn't have a '%s' key! Assuming that the share is not guest accessible.", group, KEY_GUEST_OK);
+ guest_ok = FALSE;
+ }
+
+ g_assert (path != NULL);
+ g_assert (group != NULL);
+
+ info = g_new (ShareInfo, 1);
+ info->path = path;
+ info->share_name = g_strdup (group);
+ info->comment = comment;
+ info->is_writable = is_writable;
+ info->guest_ok = guest_ok;
+
+ add_share_info_to_hashes (info);
+}
+
+static void
+replace_shares_from_key_file (GKeyFile *key_file)
+{
+ gsize num_groups;
+ char **group_names;
+ gsize i;
+
+ group_names = g_key_file_get_groups (key_file, &num_groups);
+
+ /* FIXME: In add_key_group_to_hashes(), we simply ignore key groups
+ * which have invalid data (i.e. no path). We could probably accumulate a
+ * GError with the list of invalid groups and propagate it upwards.
+ */
+ for (i = 0; i < num_groups; i++) {
+ g_assert (group_names[i] != NULL);
+ add_key_group_to_hashes (key_file, group_names[i]);
+ }
+
+ g_strfreev (group_names);
+}
+
+static gboolean
+refresh_shares (GError **error)
+{
+ GKeyFile *key_file;
+ char *argv[1];
+ GError *real_error;
+
+ free_all_shares ();
+
+ if (throw_error_on_refresh) {
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("Failed"));
+ return FALSE;
+ }
+
+ argv[0] = "info";
+
+ real_error = NULL;
+ if (!net_usershare_run (G_N_ELEMENTS (argv), argv, &key_file, &real_error)) {
+ g_message ("Called \"net usershare info\" but it failed: %s", real_error->message);
+ g_propagate_error (error, real_error);
+ return FALSE;
+ }
+
+ g_assert (key_file != NULL);
+
+ replace_shares_from_key_file (key_file);
+ g_key_file_free (key_file);
+
+ return TRUE;
+}
+
+static gboolean
+refresh_if_needed (GError **error)
+{
+ gboolean retval;
+
+ if (refresh_timestamp_update_counter == 0) {
+ time_t new_timestamp;
+
+ refresh_timestamp_update_counter = NUM_CALLS_BETWEEN_TIMESTAMP_UPDATES;
+
+ new_timestamp = time (NULL);
+ if (new_timestamp - refresh_timestamp > TIMESTAMP_THRESHOLD) {
+ /* g_message ("REFRESHING SHARES"); */
+ retval = refresh_shares (error);
+ } else
+ retval = TRUE;
+
+ refresh_timestamp = new_timestamp;
+ } else {
+ refresh_timestamp_update_counter--;
+ retval = TRUE;
+ }
+
+ return retval;
+}
+
+static ShareInfo *
+copy_share_info (ShareInfo *info)
+{
+ ShareInfo *copy;
+
+ if (!info)
+ return NULL;
+
+ copy = g_new (ShareInfo, 1);
+ copy->path = g_strdup (info->path);
+ copy->share_name = g_strdup (info->share_name);
+ copy->comment = g_strdup (info->comment);
+ copy->is_writable = info->is_writable;
+ copy->guest_ok = info->guest_ok;
+
+ return copy;
+}
+
+/**
+ * shares_supports_guest_ok:
+ * @supports_guest_ok_ret: Location to store whether "usershare allow guests"
+ * is enabled.
+ * @error: Location to store error, or #NULL.
+ *
+ * Determines whether the option "usershare allow guests" is enabled in samba
+ * config as shown by testparm.
+ *
+ * Return value: #TRUE if if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned
+ * in the @error argument, and *@ret_info_list will be set to #FALSE.
+ **/
+gboolean
+shares_supports_guest_ok (gboolean *supports_guest_ok_ret, GError **error)
+{
+ gboolean retval;
+ gboolean result;
+ char *stdout_contents;
+ char *stderr_contents;
+ int exit_status;
+ int exit_code;
+
+ *supports_guest_ok_ret = FALSE;
+
+ result = g_spawn_command_line_sync ("testparm -s --parameter-name='usershare allow guests'",
+ &stdout_contents,
+ &stderr_contents,
+ &exit_status,
+ error);
+ if (!result)
+ return FALSE;
+
+ retval = FALSE;
+
+ if (!WIFEXITED (exit_status)) {
+ if (WIFSIGNALED (exit_status)) {
+ int signal_num;
+
+ signal_num = WTERMSIG (exit_status);
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("Samba's testparm returned with signal %d"),
+ signal_num);
+ } else
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("Samba's testparm failed for an unknown reason"));
+
+ goto out;
+ }
+
+ exit_code = WEXITSTATUS (exit_status);
+ if (exit_code != 0) {
+ char *str;
+ char *message;
+
+ /* stderr_contents is in the system locale encoding, not UTF-8 */
+
+ str = g_locale_to_utf8 (stderr_contents, -1, NULL, NULL, NULL);
+
+ if (str && str[0])
+ message = g_strdup_printf (_("Samba's testparm returned error %d: %s"), exit_code, str);
+ else
+ message = g_strdup_printf (_("Samba's testparm returned error %d"), exit_code);
+
+ g_free (str);
+
+ g_set_error (error,
+ G_SPAWN_ERROR,
+ G_SPAWN_ERROR_FAILED,
+ "%s",
+ message);
+
+ g_free (message);
+
+ goto out;
+ }
+
+ retval = TRUE;
+ *supports_guest_ok_ret = (g_ascii_strncasecmp (stdout_contents, "Yes", 3) == 0);
+
+ out:
+ g_free (stdout_contents);
+ g_free (stderr_contents);
+
+ return retval;
+}
+
+static gboolean
+add_share (ShareInfo *info, GError **error)
+{
+ char *argv[7];
+ int argc;
+ ShareInfo *copy;
+ GKeyFile *key_file;
+ GError *real_error;
+ gboolean supports_success;
+ gboolean supports_guest_ok;
+ gboolean net_usershare_success;
+
+ /* g_message ("add_share() start"); */
+
+ if (throw_error_on_add) {
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("Failed"));
+ g_message ("add_share() end FAIL");
+ return FALSE;
+ }
+
+ supports_success = shares_supports_guest_ok (&supports_guest_ok, error);
+ if (!supports_success)
+ return FALSE;
+
+ argv[0] = "add";
+ argv[1] = "-l";
+ argv[2] = info->share_name;
+ argv[3] = info->path;
+ argv[4] = info->comment;
+ argv[5] = info->is_writable ? "Everyone:F" : g_strdup_printf ("Everyone:R,%s:F", g_get_user_name ());
+
+ if (supports_guest_ok) {
+ argv[6] = info->guest_ok ? "guest_ok=y" : "guest_ok=n";
+ argc = 7;
+ } else
+ argc = 6;
+
+ real_error = NULL;
+ net_usershare_success = net_usershare_run (argc, argv, &key_file, &real_error);
+ if (!info->is_writable) g_free (argv[5]);
+
+ if (!net_usershare_success) {
+ g_message ("Called \"net usershare add\" but it failed: %s", real_error->message);
+ g_propagate_error (error, real_error);
+ return FALSE;
+ }
+
+ replace_shares_from_key_file (key_file);
+
+ copy = copy_share_info (info);
+ add_share_info_to_hashes (copy);
+
+ /* g_message ("add_share() end SUCCESS"); */
+
+ return TRUE;
+}
+
+static gboolean
+remove_share (const char *path, GError **error)
+{
+ ShareInfo *old_info;
+ char *argv[2];
+ GError *real_error;
+
+ /* g_message ("remove_share() start"); */
+
+ if (throw_error_on_remove) {
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ "Failed");
+ g_message ("remove_share() end FAIL");
+ return FALSE;
+ }
+
+ old_info = lookup_share_by_path (path);
+ if (!old_info) {
+ char *display_name;
+
+ display_name = g_filename_display_name (path);
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_NONEXISTENT,
+ _("Cannot remove the share for path %s: that path is not shared"),
+ display_name);
+ g_free (display_name);
+
+ g_message ("remove_share() end FAIL: path %s was not in our hashes", path);
+ return FALSE;
+ }
+
+ argv[0] = "delete";
+ argv[1] = old_info->share_name;
+
+ real_error = NULL;
+ if (!net_usershare_run (G_N_ELEMENTS (argv), argv, NULL, &real_error)) {
+ g_message ("Called \"net usershare delete\" but it failed: %s", real_error->message);
+ g_propagate_error (error, real_error);
+ g_message ("remove_share() end FAIL");
+ return FALSE;
+ }
+
+ remove_share_info_from_hashes (old_info);
+ shares_free_share_info (old_info);
+
+ /* g_message ("remove_share() end SUCCESS"); */
+
+ return TRUE;
+}
+
+static gboolean
+modify_share (const char *old_path, ShareInfo *info, GError **error)
+{
+ ShareInfo *old_info;
+
+ /* g_message ("modify_share() start"); */
+
+ old_info = lookup_share_by_path (old_path);
+ if (old_info == NULL) {
+ /*g_message ("modify_share() end; calling add_share() instead");*/
+ return add_share (info, error);
+ }
+
+ g_assert (old_info != NULL);
+
+ if (strcmp (info->path, old_info->path) != 0) {
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("Cannot change the path of an existing share; please remove the old share first and add a new one"));
+ g_message ("modify_share() end FAIL: tried to change the path in a share!");
+ return FALSE;
+ }
+
+ if (throw_error_on_modify) {
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ "Failed");
+ g_message ("modify_share() end FAIL");
+ return FALSE;
+ }
+
+ /* Although "net usershare add" will modify an existing share if it has the same share name
+ * as the one that gets passed in, our semantics are different. We have a one-to-one mapping
+ * between paths and share names; "net usershare" supports a one-to-many mapping from paths
+ * to share names. So, we must first remove the old share and then add the new/modified one.
+ */
+
+ if (!remove_share (old_path, error)) {
+ g_message ("modify_share() end FAIL: error when removing old share");
+ return FALSE;
+ }
+
+ /* g_message ("modify_share() end: will call add_share() with the new share info"); */
+ return add_share (info, error);
+}
+
+
+
+/* Public API */
+
+GQuark
+shares_error_quark (void)
+{
+ static GQuark quark;
+
+ if (quark == 0)
+ quark = g_quark_from_string ("mate-file-manager-shares-error-quark"); /* not from_static_string since we are a module */
+
+ return quark;
+}
+
+/**
+ * shares_free_share_info:
+ * @info: A #ShareInfo structure.
+ *
+ * Frees a #ShareInfo structure.
+ **/
+void
+shares_free_share_info (ShareInfo *info)
+{
+ g_assert (info != NULL);
+
+ g_free (info->path);
+ g_free (info->share_name);
+ g_free (info->comment);
+ g_free (info);
+}
+
+/**
+ * shares_get_path_is_shared:
+ * @path: A full path name ("/foo/bar/baz") in file system encoding.
+ * @ret_is_shared: Location to store result value (#TRUE if the path is shared, #FALSE otherwise)
+ * @error: Location to store error, or #NULL.
+ *
+ * Checks whether a path is shared through Samba.
+ *
+ * Return value: #TRUE if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned in the
+ * @error argument, and *@ret_is_shared will be set to #FALSE.
+ **/
+gboolean
+shares_get_path_is_shared (const char *path, gboolean *ret_is_shared, GError **error)
+{
+ g_assert (ret_is_shared != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error)) {
+ *ret_is_shared = FALSE;
+ return FALSE;
+ }
+
+ *ret_is_shared = (lookup_share_by_path (path) != NULL);
+
+ return TRUE;
+}
+
+/**
+ * shares_get_share_info_for_path:
+ * @path: A full path name ("/foo/bar/baz") in file system encoding.
+ * @ret_share_info: Location to store result with the share's info - on return,
+ * will be non-NULL if the path is indeed shared, or #NULL if the path is not
+ * shared. You must free the non-NULL value with shares_free_share_info().
+ * @error: Location to store error, or #NULL.
+ *
+ * Queries the information for a shared path: its share name, its read-only status, etc.
+ *
+ * Return value: #TRUE if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned in the
+ * @error argument, and *@ret_share_info will be set to #NULL.
+ **/
+gboolean
+shares_get_share_info_for_path (const char *path, ShareInfo **ret_share_info, GError **error)
+{
+ ShareInfo *info;
+
+ g_assert (path != NULL);
+ g_assert (ret_share_info != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error)) {
+ *ret_share_info = NULL;
+ return FALSE;
+ }
+
+ info = lookup_share_by_path (path);
+ *ret_share_info = copy_share_info (info);
+
+ return TRUE;
+}
+
+/**
+ * shares_get_share_name_exists:
+ * @share_name: Name of a share.
+ * @ret_exists: Location to store return value; #TRUE if the share name exists, #FALSE otherwise.
+ *
+ * Queries whether a share name already exists in the user's list of shares.
+ *
+ * Return value: #TRUE if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned in the
+ * @error argument, and *@ret_exists will be set to #FALSE.
+ **/
+gboolean
+shares_get_share_name_exists (const char *share_name, gboolean *ret_exists, GError **error)
+{
+ g_assert (share_name != NULL);
+ g_assert (ret_exists != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error)) {
+ *ret_exists = FALSE;
+ return FALSE;
+ }
+
+ *ret_exists = (lookup_share_by_share_name (share_name) != NULL);
+
+ return TRUE;
+}
+
+/**
+ * shares_get_share_info_for_share_name:
+ * @share_name: Name of a share.
+ * @ret_share_info: Location to store result with the share's info - on return,
+ * will be non-NULL if there is a share for the specified name, or #NULL if no
+ * share has such name. You must free the non-NULL value with
+ * shares_free_share_info().
+ * @error: Location to store error, or #NULL.
+ *
+ * Queries the information for the share which has a specific name.
+ *
+ * Return value: #TRUE if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned in the
+ * @error argument, and *@ret_share_info will be set to #NULL.
+ **/
+gboolean
+shares_get_share_info_for_share_name (const char *share_name, ShareInfo **ret_share_info, GError **error)
+{
+ ShareInfo *info;
+
+ g_assert (share_name != NULL);
+ g_assert (ret_share_info != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error)) {
+ *ret_share_info = NULL;
+ return FALSE;
+ }
+
+ info = lookup_share_by_share_name (share_name);
+ *ret_share_info = copy_share_info (info);
+
+ return TRUE;
+}
+
+/**
+ * shares_modify_share:
+ * @old_path: Path of the share to modify, or %NULL.
+ * @info: Info of the share to modify/add, or %NULL to delete a share.
+ * @error: Location to store error, or #NULL.
+ *
+ * Can add, modify, or delete shares. To add a share, pass %NULL for @old_path,
+ * and a non-null @info. To modify a share, pass a non-null @old_path and
+ * non-null @info; in this case, @info->path must have the same contents as
+ * @old_path. To remove a share, pass a non-NULL @old_path and a %NULL @info.
+ *
+ * Return value: TRUE if the share could be modified, FALSE otherwise. If this returns
+ * FALSE, then the error information will be placed in @error.
+ **/
+gboolean
+shares_modify_share (const char *old_path, ShareInfo *info, GError **error)
+{
+ g_assert ((old_path == NULL && info != NULL)
+ || (old_path != NULL && info == NULL)
+ || (old_path != NULL && info != NULL));
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error))
+ return FALSE;
+
+ if (old_path == NULL)
+ return add_share (info, error);
+ else if (info == NULL)
+ return remove_share (old_path, error);
+ else
+ return modify_share (old_path, info, error);
+}
+
+static void
+copy_to_slist_cb (gpointer key, gpointer value, gpointer data)
+{
+ ShareInfo *info;
+ ShareInfo *copy;
+ GSList **list;
+
+ info = value;
+ list = data;
+
+ copy = copy_share_info (info);
+ *list = g_slist_prepend (*list, copy);
+}
+
+/**
+ * shares_get_share_info_list:
+ * @ret_info_list: Location to store the return value, which is a list
+ * of #ShareInfo structures. Free this with shares_free_share_info_list().
+ * @error: Location to store error, or #NULL.
+ *
+ * Gets the list of shared folders and their information.
+ *
+ * Return value: #TRUE if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned in the
+ * @error argument, and *@ret_info_list will be set to #NULL.
+ **/
+gboolean
+shares_get_share_info_list (GSList **ret_info_list, GError **error)
+{
+ g_assert (ret_info_list != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error)) {
+ *ret_info_list = NULL;
+ return FALSE;
+ }
+
+ *ret_info_list = NULL;
+ g_hash_table_foreach (path_share_info_hash, copy_to_slist_cb, ret_info_list);
+
+ return TRUE;
+}
+
+/**
+ * shares_free_share_info_list:
+ * @list: List of #ShareInfo structures, or %NULL.
+ *
+ * Frees a list of #ShareInfo structures as returned by shares_get_share_info_list().
+ **/
+void
+shares_free_share_info_list (GSList *list)
+{
+ GSList *l;
+
+ for (l = list; l; l = l->next) {
+ ShareInfo *info;
+
+ info = l->data;
+ shares_free_share_info (l->data);
+ }
+
+ g_slist_free (list);
+}
+
+void
+shares_set_debug (gboolean error_on_refresh,
+ gboolean error_on_add,
+ gboolean error_on_modify,
+ gboolean error_on_remove)
+{
+ throw_error_on_refresh = error_on_refresh;
+ throw_error_on_add = error_on_add;
+ throw_error_on_modify = error_on_modify;
+ throw_error_on_remove = error_on_remove;
+}