/* $Id$ */ /*- * Copyright (c) 2005 Benedikt Meurer * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_SYS_MOUNT_H #include #endif #ifdef HAVE_DIRENT_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_MEMORY_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #include #include /* %&§$!# IRIX */ #if defined(__sgi__) && !defined(dirfd) #define dirfd(dp) (((DIR *) dp)->__dd_fd) #endif typedef struct _ThunarVfsScandirHandle ThunarVfsScandirHandle; #ifdef __FreeBSD__ static gboolean thunar_vfs_scandir_collect_fast (ThunarVfsScandirHandle *handle, ThunarVfsPath *path, GList **directoriesp); #endif static gboolean thunar_vfs_scandir_collect_slow (ThunarVfsScandirHandle *handle, ThunarVfsPath *path, GList **directoriesp); static gboolean thunar_vfs_scandir_collect (ThunarVfsScandirHandle *handle, ThunarVfsPath *path); struct _ThunarVfsScandirHandle { ThunarVfsScandirFlags flags; GCompareFunc func; GList *path_list; gchar fname[THUNAR_VFS_PATH_MAXSTRLEN]; }; #ifdef __FreeBSD__ static gboolean thunar_vfs_scandir_collect_fast (ThunarVfsScandirHandle *handle, ThunarVfsPath *path, GList **directoriesp) { #define statfsp ((struct statfs *) (dbuf)) ThunarVfsPath *child; struct dirent *dp; struct stat statb; gboolean succeed = FALSE; gchar *dbuf; guint dlen; glong base; gint sverrno; gint size; gint loc; gint fd; gint n; /* try to open the file (the file name is placed in handle->fname) */ fd = open (handle->fname, O_RDONLY | ((handle->flags & THUNAR_VFS_SCANDIR_FOLLOW_LINKS) ? 0 : O_NOFOLLOW)); if (G_UNLIKELY (fd < 0)) { /* translate EMLINK to ENOTDIR */ if (G_UNLIKELY (errno == EMLINK)) errno = ENOTDIR; return FALSE; } /* stat the file */ if (fstat (fd, &statb) < 0) goto done; /* verify that we have a directory here */ if (!S_ISDIR (statb.st_mode)) { errno = ENOTDIR; goto done; } /* calculate the directory buffer size */ dlen = statb.st_blksize * 4; if (G_UNLIKELY ((dlen % DIRBLKSIZ) != 0)) dlen = ((dlen + DIRBLKSIZ - 1) / DIRBLKSIZ) * DIRBLKSIZ; if (G_UNLIKELY (dlen < sizeof (struct statfs))) dlen = sizeof (struct statfs); /* allocate the directory buffer, which is * also used to store the statfs(2) results. */ dbuf = alloca (dlen); /* determine the file system details */ if (fstatfs (fd, statfsp) < 0) goto done; /* check if we have a unionfs here, which requires special processing (not provided by the fast collector) */ if (memcmp (statfsp->f_fstypename, "unionfs", 8) == 0 || (statfsp->f_flags & MNT_UNION) != 0) { /* fallback to the slower collector */ succeed = thunar_vfs_scandir_collect_slow (handle, path, directoriesp); } else { /* read the directory content */ for (base = loc = size = 0;;) { /* check if we need to fill the buffer again */ if (loc >= size) { /* read the next chunk */ size = getdirentries (fd, dbuf, dlen, &base); if (size <= 0) break; loc = 0; } /* grab the pointer to the next entry */ dp = (struct dirent *) (dbuf + loc); if (G_UNLIKELY (((gulong) dp & 0x03) != 0)) break; /* verify the next record length */ if (G_UNLIKELY (dp->d_reclen <= 0 || dp->d_reclen > dlen + 1 - loc)) break; /* adjust the location pointer */ loc += dp->d_reclen; /* verify the inode and type */ if (G_UNLIKELY (dp->d_ino == 0 || dp->d_type == DT_WHT)) continue; /* ignore '.' and '..' entries */ if (G_UNLIKELY (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))) continue; /* add the child path to the path list */ child = thunar_vfs_path_relative (path, dp->d_name); if (handle->func != NULL) handle->path_list = g_list_insert_sorted (handle->path_list, child, handle->func); else handle->path_list = g_list_prepend (handle->path_list, child); /* check if we want to collect children for recursive scanning */ if (G_UNLIKELY (directoriesp != NULL)) { /* DT_UNKNOWN must be handled for certain file systems */ if (G_UNLIKELY (dp->d_type == DT_UNKNOWN)) { /* determine the absolute path to the child */ if (!thunar_vfs_path_to_string (child, handle->fname, sizeof (handle->fname), NULL) < 0) { errno = ENAMETOOLONG; goto done; } /* stat the child (according to the FOLLOW_LINKS policy) */ n = ((handle->flags & THUNAR_VFS_SCANDIR_FOLLOW_LINKS) == 0) ? lstat (handle->fname, &statb) : stat (handle->fname, &statb); /* check if we have a directory here */ if (n == 0 && S_ISDIR (statb.st_mode)) dp->d_type = DT_DIR; } /* check if we have a directory */ if (dp->d_type == DT_DIR) *directoriesp = g_list_prepend (*directoriesp, child); } } succeed = TRUE; } done: sverrno = errno; close (fd); errno = sverrno; return succeed; #undef statfsp } #endif static gboolean thunar_vfs_scandir_collect_slow (ThunarVfsScandirHandle *handle, ThunarVfsPath *path, GList **directoriesp) { ThunarVfsPath *child; struct dirent dbuf; struct dirent *dp; gint sverrno; gint n; DIR *dirp; /* try to open the directory (handle->buffer still * contains the absolute path when we get here!). */ dirp = opendir (handle->fname); if (G_UNLIKELY (dirp == NULL)) return FALSE; /* verify that the directory is really the directory we want * to open. If not, we've probably detected a race condition, * so we'll better stop rather than doing anything stupid * (remember, this method is also used in collecting * files for the unlink job!!). Better safe than sorry! */ if (G_UNLIKELY ((handle->flags & THUNAR_VFS_SCANDIR_FOLLOW_LINKS) == 0)) { struct stat fstatb; struct stat lstatb; /* stat the just opened directory */ if (fstat (dirfd (dirp), &fstatb) < 0) goto error; /* stat the path (without following links) */ if (lstat (handle->fname, &lstatb) < 0) goto error; /* check that we have the same file here */ if (fstatb.st_ino != lstatb.st_ino || fstatb.st_dev != lstatb.st_dev) { errno = ENOTDIR; goto error; } } /* read the directory content */ for (;;) { /* read the next directory entry */ if (readdir_r (dirp, &dbuf, &dp) < 0 || dp == NULL) break; /* ignore '.' and '..' entries */ if (G_UNLIKELY (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))) continue; /* add the child path to the path list */ child = thunar_vfs_path_relative (path, dp->d_name); if (handle->func != NULL) handle->path_list = g_list_insert_sorted (handle->path_list, child, handle->func); else handle->path_list = g_list_prepend (handle->path_list, child); /* check if we want to collect children for recursive scanning */ if (G_UNLIKELY (directoriesp != NULL)) { struct stat statb; /* determine the absolute path to the child */ if (thunar_vfs_path_to_string (child, handle->fname, sizeof (handle->fname), NULL) < 0) { errno = ENAMETOOLONG; goto error; } /* stat the child (according to the FOLLOW_LINKS policy) */ n = ((handle->flags & THUNAR_VFS_SCANDIR_FOLLOW_LINKS) == 0) ? lstat (handle->fname, &statb) : stat (handle->fname, &statb); /* check if we have a directory here */ if (n == 0 && S_ISDIR (statb.st_mode)) *directoriesp = g_list_prepend (*directoriesp, child); } } closedir (dirp); return TRUE; error: sverrno = errno; closedir (dirp); errno = sverrno; return FALSE; } static gboolean thunar_vfs_scandir_collect (ThunarVfsScandirHandle *handle, ThunarVfsPath *path) { gboolean succeed = FALSE; GList *directories = NULL; GList *lp; gint sverrno; /* determine the absolute path */ if (thunar_vfs_path_to_string (path, handle->fname, sizeof (handle->fname), NULL) < 0) { errno = ENAMETOOLONG; return FALSE; } #ifdef __FreeBSD__ /* We can some nice things in FreeBSD */ succeed = thunar_vfs_scandir_collect_fast (handle, path, (handle->flags & THUNAR_VFS_SCANDIR_RECURSIVE) ? &directories : NULL); #else /* use the generic (slower) collector */ succeed = thunar_vfs_scandir_collect_slow (handle, path, (handle->flags & THUNAR_VFS_SCANDIR_RECURSIVE) ? &directories : NULL); #endif /* check if we want to recurse */ if (G_UNLIKELY (directories != NULL)) { /* perform the recursion */ for (lp = directories; lp != NULL && succeed; lp = lp->next) { succeed = thunar_vfs_scandir_collect (handle, lp->data); if (G_UNLIKELY (!succeed)) { /* we can ignore certain errors here */ if (errno == EACCES || errno == EMLINK || errno == ENOTDIR || errno == ENOENT || errno == EPERM) succeed = TRUE; } } /* release the directory list */ sverrno = errno; g_list_free (directories); errno = sverrno; } return succeed; } /** * thunar_vfs_scandir: * @path * @flags * @func * @error : return location for errors or %NULL. * * The caller is responsible to free the returned list * using thunar_vfs_path_list_free() when no longer * needed. * * Return value: **/ GList* thunar_vfs_scandir (ThunarVfsPath *path, ThunarVfsScandirFlags flags, GCompareFunc func, GError **error) { ThunarVfsScandirHandle handle; g_return_val_if_fail (path != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* initialize the scandir handle */ handle.flags = flags; handle.func = func; handle.path_list = NULL; /* collect the paths */ if (!thunar_vfs_scandir_collect (&handle, path)) { /* forward the error */ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), g_strerror (errno)); thunar_vfs_path_list_free (handle.path_list); return NULL; } return handle.path_list; } #define __THUNAR_VFS_SCANDIR_C__ #include