From fb045fd28691358757a021f81064480885939ab5 Mon Sep 17 00:00:00 2001 From: Stefano Karapetsas Date: Fri, 18 Oct 2013 15:12:48 +0200 Subject: Add share extension --- share/shares.c | 1023 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1023 insertions(+) create mode 100644 share/shares.c (limited to 'share/shares.c') 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 +#include +#include +#include +#include +#include +#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: jeallison@novell.com 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; +} -- cgit v1.2.1