diff --git a/gio/glocalfile.c b/gio/glocalfile.c index 30fa2281c..064755981 100644 --- a/gio/glocalfile.c +++ b/gio/glocalfile.c @@ -109,7 +109,7 @@ G_DEFINE_TYPE_WITH_CODE (GLocalFile, g_local_file, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_FILE, g_local_file_file_iface_init)) -static char *find_mountpoint_for (const char *file, dev_t dev); +static char *find_mountpoint_for (const char *file, dev_t dev, gboolean resolve_basename_symlink); static void g_local_file_finalize (GObject *object) @@ -791,7 +791,7 @@ get_mount_info (GFileInfo *fs_info, { mount_info = 0; - mountpoint = find_mountpoint_for (path, buf.st_dev); + mountpoint = find_mountpoint_for (path, buf.st_dev, FALSE); if (mountpoint == NULL) mountpoint = g_strdup ("/"); @@ -886,7 +886,7 @@ get_volume_for_path (const char *path) } static char * -find_mountpoint_for (const char *file, dev_t dev) +find_mountpoint_for (const char *file, dev_t dev, gboolean resolve_basename_symlink) { wchar_t *wpath; char *utf8_path; @@ -1132,7 +1132,7 @@ g_local_file_find_enclosing_mount (GFile *file, if (g_lstat (local->filename, &buf) != 0) goto error; - mountpoint = find_mountpoint_for (local->filename, buf.st_dev); + mountpoint = find_mountpoint_for (local->filename, buf.st_dev, FALSE); if (mountpoint == NULL) goto error; @@ -1584,19 +1584,65 @@ expand_symlink (const char *link) } static char * -get_parent (const char *path, +expand_symlinks (const char *path, + dev_t *dev) +{ + char *tmp, *target; + GStatBuf target_stat; + int num_recursions; + + target = g_strdup (path); + + num_recursions = 0; + do + { + if (g_lstat (target, &target_stat) != 0) + { + g_free (target); + return NULL; + } + + if (S_ISLNK (target_stat.st_mode)) + { + tmp = target; + target = expand_symlink (target); + g_free (tmp); + } + + num_recursions++; + +#ifdef MAXSYMLINKS + if (num_recursions > MAXSYMLINKS) +#else + /* 40 is used in kernel sources currently: + * https://github.com/torvalds/linux/include/linux/namei.h + */ + if (num_recursions > 40) +#endif + { + g_free (target); + return NULL; + } + } + while (S_ISLNK (target_stat.st_mode)); + + if (dev) + *dev = target_stat.st_dev; + + return target; +} + +static char * +get_parent (const char *path, dev_t *parent_dev) { - char *parent, *tmp; - GStatBuf parent_stat; - int num_recursions; + char *parent, *res; char *path_copy; path_copy = strip_trailing_slashes (path); parent = g_path_get_dirname (path_copy); - if (strcmp (parent, ".") == 0 || - strcmp (parent, path_copy) == 0) + if (strcmp (parent, ".") == 0) { g_free (parent); g_free (path_copy); @@ -1604,32 +1650,10 @@ get_parent (const char *path, } g_free (path_copy); - num_recursions = 0; - do { - if (g_lstat (parent, &parent_stat) != 0) - { - g_free (parent); - return NULL; - } - - if (S_ISLNK (parent_stat.st_mode)) - { - tmp = parent; - parent = expand_symlink (parent); - g_free (tmp); - } - - num_recursions++; - if (num_recursions > 12) - { - g_free (parent); - return NULL; - } - } while (S_ISLNK (parent_stat.st_mode)); + res = expand_symlinks (parent, parent_dev); + g_free (parent); - *parent_dev = parent_stat.st_dev; - - return parent; + return res; } static char * @@ -1640,10 +1664,12 @@ expand_all_symlinks (const char *path) dev_t parent_dev; parent = get_parent (path, &parent_dev); - if (parent) + if (parent == NULL) + return NULL; + + if (g_strcmp0 (parent, "/") != 0) { parent_expanded = expand_all_symlinks (parent); - g_free (parent); basename = g_path_get_basename (path); res = g_build_filename (parent_expanded, basename, NULL); g_free (basename); @@ -1651,26 +1677,40 @@ expand_all_symlinks (const char *path) } else res = g_strdup (path); - + + g_free (parent); + return res; } static char * -find_mountpoint_for (const char *file, - dev_t dev) +find_mountpoint_for (const char *file, + dev_t dev, + gboolean resolve_basename_symlink) { char *dir, *parent; dev_t dir_dev, parent_dev; - dir = g_strdup (file); + if (resolve_basename_symlink) + { + dir = expand_symlinks (file, NULL); + if (dir == NULL) + return NULL; + } + else + dir = g_strdup (file); + dir_dev = dev; - while (1) + while (g_strcmp0 (dir, "/") != 0) { parent = get_parent (dir, &parent_dev); if (parent == NULL) - return dir; - + { + g_free (dir); + return NULL; + } + if (parent_dev != dir_dev) { g_free (parent); @@ -1680,6 +1720,8 @@ find_mountpoint_for (const char *file, g_free (dir); dir = parent; } + + return dir; } char * @@ -1693,7 +1735,7 @@ _g_local_file_find_topdir_for (const char *file) if (dir == NULL) return NULL; - mountpoint = find_mountpoint_for (dir, dir_dev); + mountpoint = find_mountpoint_for (dir, dir_dev, TRUE); g_free (dir); return mountpoint; @@ -1747,7 +1789,7 @@ try_make_relative (const char *path, base2 = expand_all_symlinks (base); relative = NULL; - if (path_has_prefix (path2, base2)) + if (path2 != NULL && base2 != NULL && path_has_prefix (path2, base2)) { relative = path2 + strlen (base2); while (*relative == '/') @@ -1789,7 +1831,7 @@ _g_local_file_has_trash_dir (const char *dirname, dev_t dir_dev) if (dir_dev == home_dev) return TRUE; - topdir = find_mountpoint_for (dirname, dir_dev); + topdir = find_mountpoint_for (dirname, dir_dev, TRUE); if (topdir == NULL) return FALSE; @@ -1856,7 +1898,7 @@ _g_local_file_is_lost_found_dir (const char *path, dev_t path_dev) if (!g_str_has_suffix (path, "/lost+found")) goto out; - mount_dir = find_mountpoint_for (path, path_dev); + mount_dir = find_mountpoint_for (path, path_dev, FALSE); if (mount_dir == NULL) goto out; diff --git a/gio/tests/trash.c b/gio/tests/trash.c index 176c4b9c3..1055585e5 100644 --- a/gio/tests/trash.c +++ b/gio/tests/trash.c @@ -38,6 +38,8 @@ test_trash_not_supported (void) gchar *parent_dirname; GStatBuf parent_stat, home_stat; + g_test_bug ("251"); + /* The test assumes that tmp file is located on system internal mount. */ file = g_file_new_tmp ("test-trashXXXXXX", &stream, &error); parent_dirname = g_path_get_dirname (g_file_peek_path (file)); @@ -94,15 +96,78 @@ test_trash_not_supported (void) g_object_unref (file); } +/* Test that symlinks are properly expaned when looking for topdir (e.g. for trash folder). */ +static void +test_trash_symlinks (void) +{ + GFile *symlink; + GUnixMountEntry *target_mount, *tmp_mount, *symlink_mount, *target_over_symlink_mount; + gchar *target, *tmp, *target_over_symlink; + GError *error = NULL; + + g_test_bug ("1522"); + + /* The test assumes that ~/.local always exists. */ + target = g_build_filename (g_get_home_dir (), ".local", NULL); + target_mount = g_unix_mount_for (target, NULL); + g_assert_nonnull (target_mount); + g_test_message ("Target: %s (mount: %s)", target, g_unix_mount_get_mount_path (target_mount)); + + tmp = g_dir_make_tmp ("test-trashXXXXXX", &error); + tmp_mount = g_unix_mount_for (tmp, NULL); + g_assert_nonnull (tmp_mount); + g_test_message ("Tmp: %s (mount: %s)", tmp, g_unix_mount_get_mount_path (tmp_mount)); + + if (g_unix_mount_compare (target_mount, tmp_mount) == 0) + { + g_test_skip ("The tmp has to be on another mount than the home to run this test"); + + g_unix_mount_free (tmp_mount); + g_free (tmp); + g_unix_mount_free (target_mount); + g_free (target); + + return; + } + + symlink = g_file_new_build_filename (tmp, "symlink", NULL); + g_file_make_symbolic_link (symlink, g_get_home_dir (), NULL, &error); + g_assert_no_error (error); + + symlink_mount = g_unix_mount_for (g_file_peek_path (symlink), NULL); + g_assert_nonnull (symlink_mount); + g_test_message ("Symlink: %s (mount: %s)", g_file_peek_path (symlink), g_unix_mount_get_mount_path (symlink_mount)); + + g_assert_cmpint (g_unix_mount_compare (symlink_mount, tmp_mount), ==, 0); + + target_over_symlink = g_build_filename (g_file_peek_path (symlink), + ".local", + NULL); + target_over_symlink_mount = g_unix_mount_for (target_over_symlink, NULL); + g_assert_nonnull (symlink_mount); + g_test_message ("Target over symlink: %s (mount: %s)", target_over_symlink, g_unix_mount_get_mount_path (target_over_symlink_mount)); + + g_assert_cmpint (g_unix_mount_compare (target_over_symlink_mount, target_mount), ==, 0); + + g_unix_mount_free (target_over_symlink_mount); + g_unix_mount_free (symlink_mount); + g_free (target_over_symlink); + g_object_unref (symlink); + g_unix_mount_free (tmp_mount); + g_free (tmp); + g_unix_mount_free (target_mount); + g_free (target); +} + int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); - g_test_bug_base ("htps://gitlab.gnome.org/GNOME/glib/issues/"); - g_test_bug ("251"); + g_test_bug_base ("https://gitlab.gnome.org/GNOME/glib/issues/"); g_test_add_func ("/trash/not-supported", test_trash_not_supported); + g_test_add_func ("/trash/symlinks", test_trash_symlinks); return g_test_run (); }