/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2006-2007 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. * * Author: Alexander Larsson */ #include "config.h" #ifdef HAVE_SYS_TIME_H #include #endif #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #ifdef HAVE_GRP_H #include #endif #ifdef HAVE_PWD_H #include #endif #ifdef HAVE_SELINUX #include #endif #ifdef HAVE_XATTR #if defined HAVE_SYS_XATTR_H #include #elif defined HAVE_ATTR_XATTR_H #include #else #error "Neither nor is present but extended attribute support is enabled." #endif /* defined HAVE_SYS_XATTR_H || HAVE_ATTR_XATTR_H */ #endif /* HAVE_XATTR */ #include #include #include #include #include "glibintl.h" #ifdef G_OS_WIN32 #include #include #ifndef W_OK #define W_OK 2 #endif #ifndef R_OK #define R_OK 4 #endif #ifndef X_OK #define X_OK 0 /* not really */ #endif #ifndef S_ISREG #define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) #endif #ifndef S_ISDIR #define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) #endif #ifndef S_IXUSR #define S_IXUSR _S_IEXEC #endif #endif #include "glocalfileinfo.h" #include "gioerror.h" #include "gthemedicon.h" #include "gcontenttypeprivate.h" struct ThumbMD5Context { guint32 buf[4]; guint32 bits[2]; unsigned char in[64]; }; #ifndef G_OS_WIN32 typedef struct { char *user_name; char *real_name; } UidData; G_LOCK_DEFINE_STATIC (uid_cache); static GHashTable *uid_cache = NULL; G_LOCK_DEFINE_STATIC (gid_cache); static GHashTable *gid_cache = NULL; #endif /* !G_OS_WIN32 */ char * _g_local_file_info_create_etag (GLocalFileStat *statbuf) { glong sec, usec; sec = statbuf->st_mtime; #if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC) usec = statbuf->st_mtimensec / 1000; #elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) usec = statbuf->st_mtim.tv_nsec / 1000; #else usec = 0; #endif return g_strdup_printf ("%lu:%lu", sec, usec); } static char * _g_local_file_info_create_file_id (GLocalFileStat *statbuf) { return g_strdup_printf ("l%" G_GUINT64_FORMAT ":%" G_GUINT64_FORMAT, (guint64) statbuf->st_dev, (guint64) statbuf->st_ino); } static char * _g_local_file_info_create_fs_id (GLocalFileStat *statbuf) { return g_strdup_printf ("l%" G_GUINT64_FORMAT, (guint64) statbuf->st_dev); } #ifdef S_ISLNK static gchar * read_link (const gchar *full_name) { #ifdef HAVE_READLINK gchar *buffer; guint size; size = 256; buffer = g_malloc (size); while (1) { int read_size; read_size = readlink (full_name, buffer, size); if (read_size < 0) { g_free (buffer); return NULL; } if (read_size < size) { buffer[read_size] = 0; return buffer; } size *= 2; buffer = g_realloc (buffer, size); } #else return NULL; #endif } #endif /* S_ISLNK */ #ifdef HAVE_SELINUX /* Get the SELinux security context */ static void get_selinux_context (const char *path, GFileInfo *info, GFileAttributeMatcher *attribute_matcher, gboolean follow_symlinks) { char *context; if (!_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_SELINUX_CONTEXT)) return; if (is_selinux_enabled ()) { if (follow_symlinks) { if (lgetfilecon_raw (path, &context) < 0) return; } else { if (getfilecon_raw (path, &context) < 0) return; } if (context) { _g_file_info_set_attribute_string_by_id (info, G_FILE_ATTRIBUTE_ID_SELINUX_CONTEXT, context); freecon (context); } } } #endif #ifdef HAVE_XATTR /* Wrappers to hide away differences between (Linux) getxattr/lgetxattr and * (Mac) getxattr(..., XATTR_NOFOLLOW) */ #ifdef HAVE_XATTR_NOFOLLOW #define g_fgetxattr(fd,name,value,size) fgetxattr(fd,name,value,size,0,0) #define g_flistxattr(fd,name,size) flistxattr(fd,name,size,0) #define g_setxattr(path,name,value,size) setxattr(path,name,value,size,0,0) #else #define g_fgetxattr fgetxattr #define g_flistxattr flistxattr #define g_setxattr(path,name,value,size) setxattr(path,name,value,size,0) #endif static ssize_t g_getxattr (const char *path, const char *name, void *value, size_t size, gboolean follow_symlinks) { #ifdef HAVE_XATTR_NOFOLLOW return getxattr (path, name, value, size, 0, follow_symlinks ? 0 : XATTR_NOFOLLOW); #else if (follow_symlinks) return getxattr (path, name, value, size); else return lgetxattr (path, name, value, size); #endif } static ssize_t g_listxattr(const char *path, char *namebuf, size_t size, gboolean follow_symlinks) { #ifdef HAVE_XATTR_NOFOLLOW return listxattr (path, namebuf, size, follow_symlinks ? 0 : XATTR_NOFOLLOW); #else if (follow_symlinks) return listxattr (path, namebuf, size); else return llistxattr (path, namebuf, size); #endif } static gboolean valid_char (char c) { return c >= 32 && c <= 126 && c != '\\'; } static gboolean name_is_valid (const char *str) { while (*str) { if (!valid_char (*str++)) return FALSE; } return TRUE; } static char * hex_escape_string (const char *str, gboolean *free_return) { int num_invalid, i; char *escaped_str, *p; unsigned char c; static char *hex_digits = "0123456789abcdef"; int len; len = strlen (str); num_invalid = 0; for (i = 0; i < len; i++) { if (!valid_char (str[i])) num_invalid++; } if (num_invalid == 0) { *free_return = FALSE; return (char *)str; } escaped_str = g_malloc (len + num_invalid*3 + 1); p = escaped_str; for (i = 0; i < len; i++) { if (valid_char (str[i])) *p++ = str[i]; else { c = str[i]; *p++ = '\\'; *p++ = 'x'; *p++ = hex_digits[(c >> 4) & 0xf]; *p++ = hex_digits[c & 0xf]; } } *p = 0; *free_return = TRUE; return escaped_str; } static char * hex_unescape_string (const char *str, int *out_len, gboolean *free_return) { int i; char *unescaped_str, *p; unsigned char c; int len; len = strlen (str); if (strchr (str, '\\') == NULL) { if (out_len) *out_len = len; *free_return = FALSE; return (char *)str; } unescaped_str = g_malloc (len + 1); p = unescaped_str; for (i = 0; i < len; i++) { if (str[i] == '\\' && str[i+1] == 'x' && len - i >= 4) { c = (g_ascii_xdigit_value (str[i+2]) << 4) | g_ascii_xdigit_value (str[i+3]); *p++ = c; i += 3; } else *p++ = str[i]; } *p++ = 0; if (out_len) *out_len = p - unescaped_str; *free_return = TRUE; return unescaped_str; } static void escape_xattr (GFileInfo *info, const char *gio_attr, /* gio attribute name */ const char *value, /* Is zero terminated */ size_t len /* not including zero termination */) { char *escaped_val; gboolean free_escaped_val; escaped_val = hex_escape_string (value, &free_escaped_val); g_file_info_set_attribute_string (info, gio_attr, escaped_val); if (free_escaped_val) g_free (escaped_val); } static void get_one_xattr (const char *path, GFileInfo *info, const char *gio_attr, const char *xattr, gboolean follow_symlinks) { char value[64]; char *value_p; ssize_t len; len = g_getxattr (path, xattr, value, sizeof (value)-1, follow_symlinks); value_p = NULL; if (len >= 0) value_p = value; else if (len == -1 && errno == ERANGE) { len = g_getxattr (path, xattr, NULL, 0, follow_symlinks); if (len < 0) return; value_p = g_malloc (len+1); len = g_getxattr (path, xattr, value_p, len, follow_symlinks); if (len < 0) { g_free (value_p); return; } } else return; /* Null terminate */ value_p[len] = 0; escape_xattr (info, gio_attr, value_p, len); if (value_p != value) g_free (value_p); } #endif /* defined HAVE_XATTR */ static void get_xattrs (const char *path, gboolean user, GFileInfo *info, GFileAttributeMatcher *matcher, gboolean follow_symlinks) { #ifdef HAVE_XATTR gboolean all; gsize list_size; ssize_t list_res_size; size_t len; char *list; const char *attr, *attr2; if (user) all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr"); else all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr-sys"); if (all) { list_res_size = g_listxattr (path, NULL, 0, follow_symlinks); if (list_res_size == -1 || list_res_size == 0) return; list_size = list_res_size; list = g_malloc (list_size); retry: list_res_size = g_listxattr (path, list, list_size, follow_symlinks); if (list_res_size == -1 && errno == ERANGE) { list_size = list_size * 2; list = g_realloc (list, list_size); goto retry; } if (list_res_size == -1) return; attr = list; while (list_res_size > 0) { if ((user && g_str_has_prefix (attr, "user.")) || (!user && !g_str_has_prefix (attr, "user."))) { char *escaped_attr, *gio_attr; gboolean free_escaped_attr; if (user) { escaped_attr = hex_escape_string (attr + 5, &free_escaped_attr); gio_attr = g_strconcat ("xattr::", escaped_attr, NULL); } else { escaped_attr = hex_escape_string (attr, &free_escaped_attr); gio_attr = g_strconcat ("xattr-sys::", escaped_attr, NULL); } if (free_escaped_attr) g_free (escaped_attr); get_one_xattr (path, info, gio_attr, attr, follow_symlinks); g_free (gio_attr); } len = strlen (attr) + 1; attr += len; list_res_size -= len; } g_free (list); } else { while ((attr = g_file_attribute_matcher_enumerate_next (matcher)) != NULL) { char *unescaped_attribute, *a; gboolean free_unescaped_attribute; attr2 = strchr (attr, ':'); if (attr2) { attr2 += 2; /* Skip '::' */ unescaped_attribute = hex_unescape_string (attr2, NULL, &free_unescaped_attribute); if (user) a = g_strconcat ("user.", unescaped_attribute, NULL); else a = unescaped_attribute; get_one_xattr (path, info, attr, a, follow_symlinks); if (user) g_free (a); if (free_unescaped_attribute) g_free (unescaped_attribute); } } } #endif /* defined HAVE_XATTR */ } #ifdef HAVE_XATTR static void get_one_xattr_from_fd (int fd, GFileInfo *info, const char *gio_attr, const char *xattr) { char value[64]; char *value_p; ssize_t len; len = g_fgetxattr (fd, xattr, value, sizeof (value) - 1); value_p = NULL; if (len >= 0) value_p = value; else if (len == -1 && errno == ERANGE) { len = g_fgetxattr (fd, xattr, NULL, 0); if (len < 0) return; value_p = g_malloc (len + 1); len = g_fgetxattr (fd, xattr, value_p, len); if (len < 0) { g_free (value_p); return; } } else return; /* Null terminate */ value_p[len] = 0; escape_xattr (info, gio_attr, value_p, len); if (value_p != value) g_free (value_p); } #endif /* defined HAVE_XATTR */ static void get_xattrs_from_fd (int fd, gboolean user, GFileInfo *info, GFileAttributeMatcher *matcher) { #ifdef HAVE_XATTR gboolean all; gsize list_size; ssize_t list_res_size; size_t len; char *list; const char *attr, *attr2; if (user) all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr"); else all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr-sys"); if (all) { list_res_size = g_flistxattr (fd, NULL, 0); if (list_res_size == -1 || list_res_size == 0) return; list_size = list_res_size; list = g_malloc (list_size); retry: list_res_size = g_flistxattr (fd, list, list_size); if (list_res_size == -1 && errno == ERANGE) { list_size = list_size * 2; list = g_realloc (list, list_size); goto retry; } if (list_res_size == -1) return; attr = list; while (list_res_size > 0) { if ((user && g_str_has_prefix (attr, "user.")) || (!user && !g_str_has_prefix (attr, "user."))) { char *escaped_attr, *gio_attr; gboolean free_escaped_attr; if (user) { escaped_attr = hex_escape_string (attr + 5, &free_escaped_attr); gio_attr = g_strconcat ("xattr::", escaped_attr, NULL); } else { escaped_attr = hex_escape_string (attr, &free_escaped_attr); gio_attr = g_strconcat ("xattr-sys::", escaped_attr, NULL); } if (free_escaped_attr) g_free (escaped_attr); get_one_xattr_from_fd (fd, info, gio_attr, attr); } len = strlen (attr) + 1; attr += len; list_res_size -= len; } g_free (list); } else { while ((attr = g_file_attribute_matcher_enumerate_next (matcher)) != NULL) { char *unescaped_attribute, *a; gboolean free_unescaped_attribute; attr2 = strchr (attr, ':'); if (attr2) { attr2++; /* Skip ':' */ unescaped_attribute = hex_unescape_string (attr2, NULL, &free_unescaped_attribute); if (user) a = g_strconcat ("user.", unescaped_attribute, NULL); else a = unescaped_attribute; get_one_xattr_from_fd (fd, info, attr, a); if (user) g_free (a); if (free_unescaped_attribute) g_free (unescaped_attribute); } } } #endif /* defined HAVE_XATTR */ } #ifdef HAVE_XATTR static gboolean set_xattr (char *filename, const char *escaped_attribute, const GFileAttributeValue *attr_value, GError **error) { char *attribute, *value; gboolean free_attribute, free_value; int val_len, res, errsv; gboolean is_user; char *a; if (attr_value == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Attribute value must be non-NULL")); return FALSE; } if (attr_value->type != G_FILE_ATTRIBUTE_TYPE_STRING) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid attribute type (string expected)")); return FALSE; } if (!name_is_valid (escaped_attribute)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid extended attribute name")); return FALSE; } if (g_str_has_prefix (escaped_attribute, "xattr::")) { escaped_attribute += strlen ("xattr::"); is_user = TRUE; } else { g_warn_if_fail (g_str_has_prefix (escaped_attribute, "xattr-sys::")); escaped_attribute += strlen ("xattr-sys::"); is_user = FALSE; } attribute = hex_unescape_string (escaped_attribute, NULL, &free_attribute); value = hex_unescape_string (attr_value->u.string, &val_len, &free_value); if (is_user) a = g_strconcat ("user.", attribute, NULL); else a = attribute; res = g_setxattr (filename, a, value, val_len); errsv = errno; if (is_user) g_free (a); if (free_attribute) g_free (attribute); if (free_value) g_free (value); if (res == -1) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), _("Error setting extended attribute '%s': %s"), escaped_attribute, g_strerror (errsv)); return FALSE; } return TRUE; } #endif void _g_local_file_info_get_parent_info (const char *dir, GFileAttributeMatcher *attribute_matcher, GLocalParentFileInfo *parent_info) { GStatBuf statbuf; int res; parent_info->extra_data = NULL; parent_info->free_extra_data = NULL; parent_info->writable = FALSE; parent_info->is_sticky = FALSE; parent_info->has_trash_dir = FALSE; parent_info->device = 0; if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_RENAME) || _g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_DELETE) || _g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_TRASH) || _g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_UNIX_IS_MOUNTPOINT)) { /* FIXME: Windows: The underlying _waccess() call in the C * library is mostly pointless as it only looks at the READONLY * FAT-style attribute of the file, it doesn't check the ACL at * all. */ parent_info->writable = (g_access (dir, W_OK) == 0); res = g_stat (dir, &statbuf); /* * The sticky bit (S_ISVTX) on a directory means that a file in that directory can be * renamed or deleted only by the owner of the file, by the owner of the directory, and * by a privileged process. */ if (res == 0) { #ifdef S_ISVTX parent_info->is_sticky = (statbuf.st_mode & S_ISVTX) != 0; #else parent_info->is_sticky = FALSE; #endif parent_info->owner = statbuf.st_uid; parent_info->device = statbuf.st_dev; /* No need to find trash dir if it's not writable anyway */ if (parent_info->writable && _g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_TRASH)) parent_info->has_trash_dir = _g_local_file_has_trash_dir (dir, statbuf.st_dev); } } } void _g_local_file_info_free_parent_info (GLocalParentFileInfo *parent_info) { if (parent_info->extra_data && parent_info->free_extra_data) parent_info->free_extra_data (parent_info->extra_data); } static void get_access_rights (GFileAttributeMatcher *attribute_matcher, GFileInfo *info, const gchar *path, GLocalFileStat *statbuf, GLocalParentFileInfo *parent_info) { /* FIXME: Windows: The underlyin _waccess() is mostly pointless */ if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_READ)) _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_READ, g_access (path, R_OK) == 0); if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_WRITE)) _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_WRITE, g_access (path, W_OK) == 0); if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_EXECUTE)) _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_EXECUTE, g_access (path, X_OK) == 0); if (parent_info) { gboolean writable; writable = FALSE; if (parent_info->writable) { if (parent_info->is_sticky) { #ifndef G_OS_WIN32 uid_t uid = geteuid (); if (uid == statbuf->st_uid || uid == parent_info->owner || uid == 0) #endif writable = TRUE; } else writable = TRUE; } if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_RENAME)) _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_RENAME, writable); if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_DELETE)) _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_DELETE, writable); if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_TRASH)) _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_ACCESS_CAN_TRASH, writable && parent_info->has_trash_dir); } } static void set_info_from_stat (GFileInfo *info, GLocalFileStat *statbuf, GFileAttributeMatcher *attribute_matcher) { GFileType file_type; file_type = G_FILE_TYPE_UNKNOWN; if (S_ISREG (statbuf->st_mode)) file_type = G_FILE_TYPE_REGULAR; else if (S_ISDIR (statbuf->st_mode)) file_type = G_FILE_TYPE_DIRECTORY; #ifndef G_OS_WIN32 else if (S_ISCHR (statbuf->st_mode) || S_ISBLK (statbuf->st_mode) || S_ISFIFO (statbuf->st_mode) #ifdef S_ISSOCK || S_ISSOCK (statbuf->st_mode) #endif ) file_type = G_FILE_TYPE_SPECIAL; #endif #ifdef S_ISLNK else if (S_ISLNK (statbuf->st_mode)) file_type = G_FILE_TYPE_SYMBOLIC_LINK; #endif g_file_info_set_file_type (info, file_type); g_file_info_set_size (info, statbuf->st_size); _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_DEVICE, statbuf->st_dev); #ifndef G_OS_WIN32 /* Pointless setting these on Windows even if they exist in the struct */ _g_file_info_set_attribute_uint64_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_INODE, statbuf->st_ino); _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_NLINK, statbuf->st_nlink); _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_UID, statbuf->st_uid); _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_GID, statbuf->st_gid); _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_RDEV, statbuf->st_rdev); #endif /* FIXME: st_mode is mostly pointless on Windows, too. Set the attribute or not? */ _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_MODE, statbuf->st_mode); #if defined (HAVE_STRUCT_STAT_ST_BLKSIZE) _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_BLOCK_SIZE, statbuf->st_blksize); #endif #if defined (HAVE_STRUCT_STAT_ST_BLOCKS) _g_file_info_set_attribute_uint64_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_BLOCKS, statbuf->st_blocks); _g_file_info_set_attribute_uint64_by_id (info, G_FILE_ATTRIBUTE_ID_STANDARD_ALLOCATED_SIZE, statbuf->st_blocks * G_GUINT64_CONSTANT (512)); #endif _g_file_info_set_attribute_uint64_by_id (info, G_FILE_ATTRIBUTE_ID_TIME_MODIFIED, statbuf->st_mtime); #if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC) _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_TIME_MODIFIED_USEC, statbuf->st_mtimensec / 1000); #elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_TIME_MODIFIED_USEC, statbuf->st_mtim.tv_nsec / 1000); #endif _g_file_info_set_attribute_uint64_by_id (info, G_FILE_ATTRIBUTE_ID_TIME_ACCESS, statbuf->st_atime); #if defined (HAVE_STRUCT_STAT_ST_ATIMENSEC) _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_TIME_ACCESS_USEC, statbuf->st_atimensec / 1000); #elif defined (HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC) _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_TIME_ACCESS_USEC, statbuf->st_atim.tv_nsec / 1000); #endif _g_file_info_set_attribute_uint64_by_id (info, G_FILE_ATTRIBUTE_ID_TIME_CHANGED, statbuf->st_ctime); #if defined (HAVE_STRUCT_STAT_ST_CTIMENSEC) _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_TIME_CHANGED_USEC, statbuf->st_ctimensec / 1000); #elif defined (HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC) _g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_TIME_CHANGED_USEC, statbuf->st_ctim.tv_nsec / 1000); #endif if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ETAG_VALUE)) { char *etag = _g_local_file_info_create_etag (statbuf); _g_file_info_set_attribute_string_by_id (info, G_FILE_ATTRIBUTE_ID_ETAG_VALUE, etag); g_free (etag); } if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ID_FILE)) { char *id = _g_local_file_info_create_file_id (statbuf); _g_file_info_set_attribute_string_by_id (info, G_FILE_ATTRIBUTE_ID_ID_FILE, id); g_free (id); } if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_ID_FILESYSTEM)) { char *id = _g_local_file_info_create_fs_id (statbuf); _g_file_info_set_attribute_string_by_id (info, G_FILE_ATTRIBUTE_ID_ID_FILESYSTEM, id); g_free (id); } } #ifndef G_OS_WIN32 static char * make_valid_utf8 (const char *name) { GString *string; const gchar *remainder, *invalid; gint remaining_bytes, valid_bytes; string = NULL; remainder = name; remaining_bytes = strlen (name); while (remaining_bytes != 0) { if (g_utf8_validate (remainder, remaining_bytes, &invalid)) break; valid_bytes = invalid - remainder; if (string == NULL) string = g_string_sized_new (remaining_bytes); g_string_append_len (string, remainder, valid_bytes); /* append U+FFFD REPLACEMENT CHARACTER */ g_string_append (string, "\357\277\275"); remaining_bytes -= valid_bytes + 1; remainder = invalid + 1; } if (string == NULL) return g_strdup (name); g_string_append (string, remainder); g_warn_if_fail (g_utf8_validate (string->str, -1, NULL)); return g_string_free (string, FALSE); } static char * convert_pwd_string_to_utf8 (char *pwd_str) { char *utf8_string; if (!g_utf8_validate (pwd_str, -1, NULL)) { utf8_string = g_locale_to_utf8 (pwd_str, -1, NULL, NULL, NULL); if (utf8_string == NULL) utf8_string = make_valid_utf8 (pwd_str); } else utf8_string = g_strdup (pwd_str); return utf8_string; } static void uid_data_free (UidData *data) { g_free (data->user_name); g_free (data->real_name); g_free (data); } /* called with lock held */ static UidData * lookup_uid_data (uid_t uid) { UidData *data; char buffer[4096]; struct passwd pwbuf; struct passwd *pwbufp; char *gecos, *comma; if (uid_cache == NULL) uid_cache = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)uid_data_free); data = g_hash_table_lookup (uid_cache, GINT_TO_POINTER (uid)); if (data) return data; data = g_new0 (UidData, 1); #if defined(HAVE_POSIX_GETPWUID_R) getpwuid_r (uid, &pwbuf, buffer, sizeof(buffer), &pwbufp); #elif defined(HAVE_NONPOSIX_GETPWUID_R) pwbufp = getpwuid_r (uid, &pwbuf, buffer, sizeof(buffer)); #else pwbufp = getpwuid (uid); #endif if (pwbufp != NULL) { if (pwbufp->pw_name != NULL && pwbufp->pw_name[0] != 0) data->user_name = convert_pwd_string_to_utf8 (pwbufp->pw_name); gecos = pwbufp->pw_gecos; if (gecos) { comma = strchr (gecos, ','); if (comma) *comma = 0; data->real_name = convert_pwd_string_to_utf8 (gecos); } } /* Default fallbacks */ if (data->real_name == NULL) { if (data->user_name != NULL) data->real_name = g_strdup (data->user_name); else data->real_name = g_strdup_printf ("user #%d", (int)uid); } if (data->user_name == NULL) data->user_name = g_strdup_printf ("%d", (int)uid); g_hash_table_replace (uid_cache, GINT_TO_POINTER (uid), data); return data; } static char * get_username_from_uid (uid_t uid) { char *res; UidData *data; G_LOCK (uid_cache); data = lookup_uid_data (uid); res = g_strdup (data->user_name); G_UNLOCK (uid_cache); return res; } static char * get_realname_from_uid (uid_t uid) { char *res; UidData *data; G_LOCK (uid_cache); data = lookup_uid_data (uid); res = g_strdup (data->real_name); G_UNLOCK (uid_cache); return res; } /* called with lock held */ static char * lookup_gid_name (gid_t gid) { char *name; char buffer[4096]; struct group gbuf; struct group *gbufp; if (gid_cache == NULL) gid_cache = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free); name = g_hash_table_lookup (gid_cache, GINT_TO_POINTER (gid)); if (name) return name; #if defined (HAVE_POSIX_GETGRGID_R) getgrgid_r (gid, &gbuf, buffer, sizeof(buffer), &gbufp); #elif defined (HAVE_NONPOSIX_GETGRGID_R) gbufp = getgrgid_r (gid, &gbuf, buffer, sizeof(buffer)); #else gbufp = getgrgid (gid); #endif if (gbufp != NULL && gbufp->gr_name != NULL && gbufp->gr_name[0] != 0) name = convert_pwd_string_to_utf8 (gbufp->gr_name); else name = g_strdup_printf("%d", (int)gid); g_hash_table_replace (gid_cache, GINT_TO_POINTER (gid), name); return name; } static char * get_groupname_from_gid (gid_t gid) { char *res; char *name; G_LOCK (gid_cache); name = lookup_gid_name (gid); res = g_strdup (name); G_UNLOCK (gid_cache); return res; } #endif /* !G_OS_WIN32 */ static char * get_content_type (const char *basename, const char *path, GLocalFileStat *statbuf, gboolean is_symlink, gboolean symlink_broken, GFileQueryInfoFlags flags, gboolean fast) { if (is_symlink && (symlink_broken || (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))) return g_strdup ("inode/symlink"); else if (statbuf != NULL && S_ISDIR(statbuf->st_mode)) return g_strdup ("inode/directory"); #ifndef G_OS_WIN32 else if (statbuf != NULL && S_ISCHR(statbuf->st_mode)) return g_strdup ("inode/chardevice"); else if (statbuf != NULL && S_ISBLK(statbuf->st_mode)) return g_strdup ("inode/blockdevice"); else if (statbuf != NULL && S_ISFIFO(statbuf->st_mode)) return g_strdup ("inode/fifo"); #endif #ifdef S_ISSOCK else if (statbuf != NULL && S_ISSOCK(statbuf->st_mode)) return g_strdup ("inode/socket"); #endif else { char *content_type; gboolean result_uncertain; content_type = g_content_type_guess (basename, NULL, 0, &result_uncertain); #ifndef G_OS_WIN32 if (!fast && result_uncertain && path != NULL) { guchar sniff_buffer[4096]; gsize sniff_length; int fd; sniff_length = _g_unix_content_type_get_sniff_len (); if (sniff_length > 4096) sniff_length = 4096; #ifdef O_NOATIME fd = g_open (path, O_RDONLY | O_NOATIME, 0); if (fd < 0 && errno == EPERM) #endif fd = g_open (path, O_RDONLY, 0); if (fd != -1) { ssize_t res; res = read (fd, sniff_buffer, sniff_length); close (fd); if (res >= 0) { g_free (content_type); content_type = g_content_type_guess (basename, sniff_buffer, res, NULL); } } } #endif return content_type; } } static void get_thumbnail_attributes (const char *path, GFileInfo *info) { GChecksum *checksum; char *uri; char *filename; char *basename; uri = g_filename_to_uri (path, NULL, NULL); checksum = g_checksum_new (G_CHECKSUM_MD5); g_checksum_update (checksum, (const guchar *) uri, strlen (uri)); g_free (uri); basename = g_strconcat (g_checksum_get_string (checksum), ".png", NULL); g_checksum_free (checksum); filename = g_build_filename (g_get_user_cache_dir (), "thumbnails", "normal", basename, NULL); if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) _g_file_info_set_attribute_byte_string_by_id (info, G_FILE_ATTRIBUTE_ID_THUMBNAIL_PATH, filename); else { g_free (filename); filename = g_build_filename (g_get_user_cache_dir (), "thumbnails", "fail", "gnome-thumbnail-factory", basename, NULL); if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_THUMBNAILING_FAILED, TRUE); } g_free (basename); g_free (filename); } #ifdef G_OS_WIN32 static void win32_get_file_user_info (const gchar *filename, gchar **group_name, gchar **user_name, gchar **real_name) { PSECURITY_DESCRIPTOR psd = NULL; DWORD sd_size = 0; /* first call calculates the size required */ wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); if ((GetFileSecurityW (wfilename, GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, NULL, sd_size, &sd_size) || (ERROR_INSUFFICIENT_BUFFER == GetLastError())) && (psd = g_try_malloc (sd_size)) != NULL && GetFileSecurityW (wfilename, GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, psd, sd_size, &sd_size)) { PSID psid = 0; BOOL defaulted; SID_NAME_USE name_use = 0; /* don't care? */ wchar_t *name = NULL; wchar_t *domain = NULL; DWORD name_len = 0; DWORD domain_len = 0; /* get the user name */ do { if (!user_name) break; if (!GetSecurityDescriptorOwner (psd, &psid, &defaulted)) break; if (!LookupAccountSidW (NULL, /* local machine */ psid, name, &name_len, domain, &domain_len, /* no domain info yet */ &name_use) && (ERROR_INSUFFICIENT_BUFFER != GetLastError())) break; name = g_try_malloc (name_len * sizeof (wchar_t)); domain = g_try_malloc (domain_len * sizeof (wchar_t)); if (name && domain && LookupAccountSidW (NULL, /* local machine */ psid, name, &name_len, domain, &domain_len, /* no domain info yet */ &name_use)) { *user_name = g_utf16_to_utf8 (name, -1, NULL, NULL, NULL); } g_free (name); g_free (domain); } while (FALSE); /* get the group name */ do { if (!group_name) break; if (!GetSecurityDescriptorGroup (psd, &psid, &defaulted)) break; if (!LookupAccountSidW (NULL, /* local machine */ psid, name, &name_len, domain, &domain_len, /* no domain info yet */ &name_use) && (ERROR_INSUFFICIENT_BUFFER != GetLastError())) break; name = g_try_malloc (name_len * sizeof (wchar_t)); domain = g_try_malloc (domain_len * sizeof (wchar_t)); if (name && domain && LookupAccountSidW (NULL, /* local machine */ psid, name, &name_len, domain, &domain_len, /* no domain info yet */ &name_use)) { *group_name = g_utf16_to_utf8 (name, -1, NULL, NULL, NULL); } g_free (name); g_free (domain); } while (FALSE); /* TODO: get real name */ g_free (psd); } g_free (wfilename); } #endif /* G_OS_WIN32 */ void _g_local_file_info_get_nostat (GFileInfo *info, const char *basename, const char *path, GFileAttributeMatcher *attribute_matcher) { g_file_info_set_name (info, basename); if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_STANDARD_DISPLAY_NAME)) { char *display_name = g_filename_display_basename (path); /* look for U+FFFD REPLACEMENT CHARACTER */ if (strstr (display_name, "\357\277\275") != NULL) { char *p = display_name; display_name = g_strconcat (display_name, _(" (invalid encoding)"), NULL); g_free (p); } g_file_info_set_display_name (info, display_name); g_free (display_name); } if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_STANDARD_EDIT_NAME)) { char *edit_name = g_filename_display_basename (path); g_file_info_set_edit_name (info, edit_name); g_free (edit_name); } if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_STANDARD_COPY_NAME)) { char *copy_name = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL); if (copy_name) _g_file_info_set_attribute_string_by_id (info, G_FILE_ATTRIBUTE_ID_STANDARD_COPY_NAME, copy_name); g_free (copy_name); } } static const char * get_icon_name (const char *path, gboolean use_symbolic, gboolean *with_fallbacks_out) { const char *name = NULL; gboolean with_fallbacks = TRUE; if (strcmp (path, g_get_home_dir ()) == 0) { name = use_symbolic ? "user-home-symbolic" : "user-home"; with_fallbacks = FALSE; } else if (strcmp (path, g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP)) == 0) { name = use_symbolic ? "user-desktop-symbolic" : "user-desktop"; with_fallbacks = FALSE; } else if (g_strcmp0 (path, g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS)) == 0) { name = use_symbolic ? "folder-documents-symbolic" : "folder-documents"; } else if (g_strcmp0 (path, g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD)) == 0) { name = use_symbolic ? "folder-download-symbolic" : "folder-download"; } else if (g_strcmp0 (path, g_get_user_special_dir (G_USER_DIRECTORY_MUSIC)) == 0) { name = use_symbolic ? "folder-music-symbolic" : "folder-music"; } else if (g_strcmp0 (path, g_get_user_special_dir (G_USER_DIRECTORY_PICTURES)) == 0) { name = use_symbolic ? "folder-pictures-symbolic" : "folder-pictures"; } else if (g_strcmp0 (path, g_get_user_special_dir (G_USER_DIRECTORY_PUBLIC_SHARE)) == 0) { name = use_symbolic ? "folder-publicshare-symbolic" : "folder-publicshare"; } else if (g_strcmp0 (path, g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES)) == 0) { name = use_symbolic ? "folder-templates-symbolic" : "folder-templates"; } else if (g_strcmp0 (path, g_get_user_special_dir (G_USER_DIRECTORY_VIDEOS)) == 0) { name = use_symbolic ? "folder-videos-symbolic" : "folder-videos"; } else { name = NULL; } if (with_fallbacks_out != NULL) *with_fallbacks_out = with_fallbacks; return name; } static GIcon * get_icon (const char *path, const char *content_type, gboolean is_folder, gboolean use_symbolic) { GIcon *icon = NULL; const char *icon_name; gboolean with_fallbacks; icon_name = get_icon_name (path, use_symbolic, &with_fallbacks); if (icon_name != NULL) { if (with_fallbacks) icon = g_themed_icon_new_with_default_fallbacks (icon_name); else icon = g_themed_icon_new (icon_name); } else { #ifdef G_OS_UNIX if (use_symbolic) icon = g_content_type_get_symbolic_icon (content_type); else #endif icon = g_content_type_get_icon (content_type); if (G_IS_THEMED_ICON (icon) && is_folder) { g_themed_icon_append_name (G_THEMED_ICON (icon), use_symbolic ? "folder-symbolic" : "folder"); } } return icon; } GFileInfo * _g_local_file_info_get (const char *basename, const char *path, GFileAttributeMatcher *attribute_matcher, GFileQueryInfoFlags flags, GLocalParentFileInfo *parent_info, GError **error) { GFileInfo *info; GLocalFileStat statbuf; #ifdef S_ISLNK struct stat statbuf2; #endif int res; gboolean stat_ok; gboolean is_symlink, symlink_broken; #ifdef G_OS_WIN32 DWORD dos_attributes; #endif char *symlink_target; GVfs *vfs; GVfsClass *class; guint64 device; info = g_file_info_new (); /* Make sure we don't set any unwanted attributes */ g_file_info_set_attribute_mask (info, attribute_matcher); _g_local_file_info_get_nostat (info, basename, path, attribute_matcher); if (attribute_matcher == NULL) { g_file_info_unset_attribute_mask (info); return info; } #ifndef G_OS_WIN32 res = g_lstat (path, &statbuf); #else { wchar_t *wpath = g_utf8_to_utf16 (path, -1, NULL, NULL, error); int len; if (wpath == NULL) { g_object_unref (info); return NULL; } len = wcslen (wpath); while (len > 0 && G_IS_DIR_SEPARATOR (wpath[len-1])) len--; if (len > 0 && (!g_path_is_absolute (path) || len > g_path_skip_root (path) - path)) wpath[len] = '\0'; res = _wstati64 (wpath, &statbuf); dos_attributes = GetFileAttributesW (wpath); g_free (wpath); } #endif if (res == -1) { int errsv = errno; /* Don't bail out if we get Permission denied (SELinux?) */ if (errsv != EACCES) { char *display_name = g_filename_display_name (path); g_object_unref (info); g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), _("Error when getting information for file '%s': %s"), display_name, g_strerror (errsv)); g_free (display_name); return NULL; } } /* Even if stat() fails, try to get as much as other attributes possible */ stat_ok = res != -1; if (stat_ok) device = statbuf.st_dev; else device = 0; #ifdef S_ISLNK is_symlink = stat_ok && S_ISLNK (statbuf.st_mode); #else is_symlink = FALSE; #endif symlink_broken = FALSE; #ifdef S_ISLNK if (is_symlink) { g_file_info_set_is_symlink (info, TRUE); /* Unless NOFOLLOW was set we default to following symlinks */ if (!(flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)) { res = stat (path, &statbuf2); /* Report broken links as symlinks */ if (res != -1) { statbuf = statbuf2; stat_ok = TRUE; } else symlink_broken = TRUE; } } #endif if (stat_ok) set_info_from_stat (info, &statbuf, attribute_matcher); #ifndef G_OS_WIN32 if (basename != NULL && basename[0] == '.') g_file_info_set_is_hidden (info, TRUE); if (basename != NULL && basename[strlen (basename) -1] == '~' && (stat_ok && S_ISREG (statbuf.st_mode))) _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_STANDARD_IS_BACKUP, TRUE); #else if (dos_attributes & FILE_ATTRIBUTE_HIDDEN) g_file_info_set_is_hidden (info, TRUE); if (dos_attributes & FILE_ATTRIBUTE_ARCHIVE) _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_DOS_IS_ARCHIVE, TRUE); if (dos_attributes & FILE_ATTRIBUTE_SYSTEM) _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_DOS_IS_SYSTEM, TRUE); #endif symlink_target = NULL; #ifdef S_ISLNK if (is_symlink) { symlink_target = read_link (path); if (symlink_target && _g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_STANDARD_SYMLINK_TARGET)) g_file_info_set_symlink_target (info, symlink_target); } #endif if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_STANDARD_CONTENT_TYPE) || _g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_STANDARD_ICON) || _g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_STANDARD_SYMBOLIC_ICON)) { char *content_type = get_content_type (basename, path, stat_ok ? &statbuf : NULL, is_symlink, symlink_broken, flags, FALSE); if (content_type) { g_file_info_set_content_type (info, content_type); if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_STANDARD_ICON) || _g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_STANDARD_SYMBOLIC_ICON)) { GIcon *icon; /* non symbolic icon */ icon = get_icon (path, content_type, S_ISDIR (statbuf.st_mode), FALSE); if (icon != NULL) { g_file_info_set_icon (info, icon); g_object_unref (icon); } /* symbolic icon */ icon = get_icon (path, content_type, S_ISDIR (statbuf.st_mode), TRUE); if (icon != NULL) { g_file_info_set_symbolic_icon (info, icon); g_object_unref (icon); } } g_free (content_type); } } if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_STANDARD_FAST_CONTENT_TYPE)) { char *content_type = get_content_type (basename, path, stat_ok ? &statbuf : NULL, is_symlink, symlink_broken, flags, TRUE); if (content_type) { _g_file_info_set_attribute_string_by_id (info, G_FILE_ATTRIBUTE_ID_STANDARD_FAST_CONTENT_TYPE, content_type); g_free (content_type); } } if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_OWNER_USER)) { char *name = NULL; #ifdef G_OS_WIN32 win32_get_file_user_info (path, NULL, &name, NULL); #else if (stat_ok) name = get_username_from_uid (statbuf.st_uid); #endif if (name) _g_file_info_set_attribute_string_by_id (info, G_FILE_ATTRIBUTE_ID_OWNER_USER, name); g_free (name); } if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_OWNER_USER_REAL)) { char *name = NULL; #ifdef G_OS_WIN32 win32_get_file_user_info (path, NULL, NULL, &name); #else if (stat_ok) name = get_realname_from_uid (statbuf.st_uid); #endif if (name) _g_file_info_set_attribute_string_by_id (info, G_FILE_ATTRIBUTE_ID_OWNER_USER_REAL, name); g_free (name); } if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_OWNER_GROUP)) { char *name = NULL; #ifdef G_OS_WIN32 win32_get_file_user_info (path, &name, NULL, NULL); #else if (stat_ok) name = get_groupname_from_gid (statbuf.st_gid); #endif if (name) _g_file_info_set_attribute_string_by_id (info, G_FILE_ATTRIBUTE_ID_OWNER_GROUP, name); g_free (name); } if (stat_ok && parent_info && parent_info->device != 0 && _g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_UNIX_IS_MOUNTPOINT) && statbuf.st_dev != parent_info->device) _g_file_info_set_attribute_boolean_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_IS_MOUNTPOINT, TRUE); if (stat_ok) get_access_rights (attribute_matcher, info, path, &statbuf, parent_info); #ifdef HAVE_SELINUX get_selinux_context (path, info, attribute_matcher, (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) == 0); #endif get_xattrs (path, TRUE, info, attribute_matcher, (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) == 0); get_xattrs (path, FALSE, info, attribute_matcher, (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) == 0); if (_g_file_attribute_matcher_matches_id (attribute_matcher, G_FILE_ATTRIBUTE_ID_THUMBNAIL_PATH)) get_thumbnail_attributes (path, info); vfs = g_vfs_get_default (); class = G_VFS_GET_CLASS (vfs); if (class->local_file_add_info) { class->local_file_add_info (vfs, path, device, attribute_matcher, info, NULL, &parent_info->extra_data, &parent_info->free_extra_data); } g_file_info_unset_attribute_mask (info); g_free (symlink_target); return info; } GFileInfo * _g_local_file_info_get_from_fd (int fd, const char *attributes, GError **error) { GLocalFileStat stat_buf; GFileAttributeMatcher *matcher; GFileInfo *info; #ifdef G_OS_WIN32 #define FSTAT _fstati64 #else #define FSTAT fstat #endif if (FSTAT (fd, &stat_buf) == -1) { int errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), _("Error when getting information for file descriptor: %s"), g_strerror (errsv)); return NULL; } info = g_file_info_new (); matcher = g_file_attribute_matcher_new (attributes); /* Make sure we don't set any unwanted attributes */ g_file_info_set_attribute_mask (info, matcher); set_info_from_stat (info, &stat_buf, matcher); #ifdef HAVE_SELINUX if (_g_file_attribute_matcher_matches_id (matcher, G_FILE_ATTRIBUTE_ID_SELINUX_CONTEXT) && is_selinux_enabled ()) { char *context; if (fgetfilecon_raw (fd, &context) >= 0) { _g_file_info_set_attribute_string_by_id (info, G_FILE_ATTRIBUTE_ID_SELINUX_CONTEXT, context); freecon (context); } } #endif get_xattrs_from_fd (fd, TRUE, info, matcher); get_xattrs_from_fd (fd, FALSE, info, matcher); g_file_attribute_matcher_unref (matcher); g_file_info_unset_attribute_mask (info); return info; } static gboolean get_uint32 (const GFileAttributeValue *value, guint32 *val_out, GError **error) { if (value->type != G_FILE_ATTRIBUTE_TYPE_UINT32) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid attribute type (uint32 expected)")); return FALSE; } *val_out = value->u.uint32; return TRUE; } #ifdef HAVE_UTIMES static gboolean get_uint64 (const GFileAttributeValue *value, guint64 *val_out, GError **error) { if (value->type != G_FILE_ATTRIBUTE_TYPE_UINT64) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid attribute type (uint64 expected)")); return FALSE; } *val_out = value->u.uint64; return TRUE; } #endif #if defined(HAVE_SYMLINK) static gboolean get_byte_string (const GFileAttributeValue *value, const char **val_out, GError **error) { if (value->type != G_FILE_ATTRIBUTE_TYPE_BYTE_STRING) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid attribute type (byte string expected)")); return FALSE; } *val_out = value->u.string; return TRUE; } #endif #ifdef HAVE_SELINUX static gboolean get_string (const GFileAttributeValue *value, const char **val_out, GError **error) { if (value->type != G_FILE_ATTRIBUTE_TYPE_STRING) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid attribute type (byte string expected)")); return FALSE; } *val_out = value->u.string; return TRUE; } #endif static gboolean set_unix_mode (char *filename, GFileQueryInfoFlags flags, const GFileAttributeValue *value, GError **error) { guint32 val = 0; int res = 0; if (!get_uint32 (value, &val, error)) return FALSE; #ifdef HAVE_SYMLINK if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) { #ifdef HAVE_LCHMOD res = lchmod (filename, val); #else struct stat statbuf; /* Calling chmod on a symlink changes permissions on the symlink. * We don't want to do this, so we need to check for a symlink */ res = g_lstat (filename, &statbuf); if (res == 0 && S_ISLNK (statbuf.st_mode)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Cannot set permissions on symlinks")); return FALSE; } else if (res == 0) res = g_chmod (filename, val); #endif } else #endif res = g_chmod (filename, val); if (res == -1) { int errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), _("Error setting permissions: %s"), g_strerror (errsv)); return FALSE; } return TRUE; } #ifdef HAVE_CHOWN static gboolean set_unix_uid_gid (char *filename, const GFileAttributeValue *uid_value, const GFileAttributeValue *gid_value, GFileQueryInfoFlags flags, GError **error) { int res; guint32 val = 0; uid_t uid; gid_t gid; if (uid_value) { if (!get_uint32 (uid_value, &val, error)) return FALSE; uid = val; } else uid = -1; if (gid_value) { if (!get_uint32 (gid_value, &val, error)) return FALSE; gid = val; } else gid = -1; #ifdef HAVE_LCHOWN if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) res = lchown (filename, uid, gid); else #endif res = chown (filename, uid, gid); if (res == -1) { int errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), _("Error setting owner: %s"), g_strerror (errsv)); return FALSE; } return TRUE; } #endif #ifdef HAVE_SYMLINK static gboolean set_symlink (char *filename, const GFileAttributeValue *value, GError **error) { const char *val; struct stat statbuf; if (!get_byte_string (value, &val, error)) return FALSE; if (val == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("symlink must be non-NULL")); return FALSE; } if (g_lstat (filename, &statbuf)) { int errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), _("Error setting symlink: %s"), g_strerror (errsv)); return FALSE; } if (!S_ISLNK (statbuf.st_mode)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SYMBOLIC_LINK, _("Error setting symlink: file is not a symlink")); return FALSE; } if (g_unlink (filename)) { int errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), _("Error setting symlink: %s"), g_strerror (errsv)); return FALSE; } if (symlink (filename, val) != 0) { int errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), _("Error setting symlink: %s"), g_strerror (errsv)); return FALSE; } return TRUE; } #endif #ifdef HAVE_UTIMES static int lazy_stat (char *filename, struct stat *statbuf, gboolean *called_stat) { int res; if (*called_stat) return 0; res = g_stat (filename, statbuf); if (res == 0) *called_stat = TRUE; return res; } static gboolean set_mtime_atime (char *filename, const GFileAttributeValue *mtime_value, const GFileAttributeValue *mtime_usec_value, const GFileAttributeValue *atime_value, const GFileAttributeValue *atime_usec_value, GError **error) { int res; guint64 val = 0; guint32 val_usec = 0; struct stat statbuf; gboolean got_stat = FALSE; struct timeval times[2] = { {0, 0}, {0, 0} }; /* ATIME */ if (atime_value) { if (!get_uint64 (atime_value, &val, error)) return FALSE; times[0].tv_sec = val; } else { if (lazy_stat (filename, &statbuf, &got_stat) == 0) { times[0].tv_sec = statbuf.st_mtime; #if defined (HAVE_STRUCT_STAT_ST_ATIMENSEC) times[0].tv_usec = statbuf.st_atimensec / 1000; #elif defined (HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC) times[0].tv_usec = statbuf.st_atim.tv_nsec / 1000; #endif } } if (atime_usec_value) { if (!get_uint32 (atime_usec_value, &val_usec, error)) return FALSE; times[0].tv_usec = val_usec; } /* MTIME */ if (mtime_value) { if (!get_uint64 (mtime_value, &val, error)) return FALSE; times[1].tv_sec = val; } else { if (lazy_stat (filename, &statbuf, &got_stat) == 0) { times[1].tv_sec = statbuf.st_mtime; #if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC) times[1].tv_usec = statbuf.st_mtimensec / 1000; #elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) times[1].tv_usec = statbuf.st_mtim.tv_nsec / 1000; #endif } } if (mtime_usec_value) { if (!get_uint32 (mtime_usec_value, &val_usec, error)) return FALSE; times[1].tv_usec = val_usec; } res = utimes (filename, times); if (res == -1) { int errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), _("Error setting modification or access time: %s"), g_strerror (errsv)); return FALSE; } return TRUE; } #endif #ifdef HAVE_SELINUX static gboolean set_selinux_context (char *filename, const GFileAttributeValue *value, GError **error) { const char *val; if (!get_string (value, &val, error)) return FALSE; if (val == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("SELinux context must be non-NULL")); return FALSE; } if (is_selinux_enabled ()) { security_context_t val_s; val_s = g_strdup (val); if (setfilecon_raw (filename, val_s) < 0) { int errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), _("Error setting SELinux context: %s"), g_strerror (errsv)); return FALSE; } g_free (val_s); } else { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("SELinux is not enabled on this system")); return FALSE; } return TRUE; } #endif gboolean _g_local_file_info_set_attribute (char *filename, const char *attribute, GFileAttributeType type, gpointer value_p, GFileQueryInfoFlags flags, GCancellable *cancellable, GError **error) { GFileAttributeValue value = { 0 }; GVfsClass *class; GVfs *vfs; _g_file_attribute_value_set_from_pointer (&value, type, value_p, FALSE); if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) == 0) return set_unix_mode (filename, flags, &value, error); #ifdef HAVE_CHOWN else if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_UID) == 0) return set_unix_uid_gid (filename, &value, NULL, flags, error); else if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_GID) == 0) return set_unix_uid_gid (filename, NULL, &value, flags, error); #endif #ifdef HAVE_SYMLINK else if (strcmp (attribute, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET) == 0) return set_symlink (filename, &value, error); #endif #ifdef HAVE_UTIMES else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED) == 0) return set_mtime_atime (filename, &value, NULL, NULL, NULL, error); else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC) == 0) return set_mtime_atime (filename, NULL, &value, NULL, NULL, error); else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS) == 0) return set_mtime_atime (filename, NULL, NULL, &value, NULL, error); else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC) == 0) return set_mtime_atime (filename, NULL, NULL, NULL, &value, error); #endif #ifdef HAVE_XATTR else if (g_str_has_prefix (attribute, "xattr::")) return set_xattr (filename, attribute, &value, error); else if (g_str_has_prefix (attribute, "xattr-sys::")) return set_xattr (filename, attribute, &value, error); #endif #ifdef HAVE_SELINUX else if (strcmp (attribute, G_FILE_ATTRIBUTE_SELINUX_CONTEXT) == 0) return set_selinux_context (filename, &value, error); #endif vfs = g_vfs_get_default (); class = G_VFS_GET_CLASS (vfs); if (class->local_file_set_attributes) { GFileInfo *info; info = g_file_info_new (); g_file_info_set_attribute (info, attribute, type, value_p); if (!class->local_file_set_attributes (vfs, filename, info, flags, cancellable, error)) { g_object_unref (info); return FALSE; } if (g_file_info_get_attribute_status (info, attribute) == G_FILE_ATTRIBUTE_STATUS_SET) { g_object_unref (info); return TRUE; } g_object_unref (info); } g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Setting attribute %s not supported"), attribute); return FALSE; } gboolean _g_local_file_info_set_attributes (char *filename, GFileInfo *info, GFileQueryInfoFlags flags, GCancellable *cancellable, GError **error) { GFileAttributeValue *value; #ifdef HAVE_CHOWN GFileAttributeValue *uid, *gid; #endif #ifdef HAVE_UTIMES GFileAttributeValue *mtime, *mtime_usec, *atime, *atime_usec; #endif #if defined (HAVE_CHOWN) || defined (HAVE_UTIMES) GFileAttributeStatus status; #endif gboolean res; GVfsClass *class; GVfs *vfs; /* Handles setting multiple specified data in a single set, and takes care of ordering restrictions when setting attributes */ res = TRUE; /* Set symlink first, since this recreates the file */ #ifdef HAVE_SYMLINK value = _g_file_info_get_attribute_value (info, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET); if (value) { if (!set_symlink (filename, value, error)) { value->status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING; res = FALSE; /* Don't set error multiple times */ error = NULL; } else value->status = G_FILE_ATTRIBUTE_STATUS_SET; } #endif #ifdef HAVE_CHOWN /* Group uid and gid setting into one call * Change ownership before permissions, since ownership changes can change permissions (e.g. setuid) */ uid = _g_file_info_get_attribute_value (info, G_FILE_ATTRIBUTE_UNIX_UID); gid = _g_file_info_get_attribute_value (info, G_FILE_ATTRIBUTE_UNIX_GID); if (uid || gid) { if (!set_unix_uid_gid (filename, uid, gid, flags, error)) { status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING; res = FALSE; /* Don't set error multiple times */ error = NULL; } else status = G_FILE_ATTRIBUTE_STATUS_SET; if (uid) uid->status = status; if (gid) gid->status = status; } #endif value = _g_file_info_get_attribute_value (info, G_FILE_ATTRIBUTE_UNIX_MODE); if (value) { if (!set_unix_mode (filename, flags, value, error)) { value->status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING; res = FALSE; /* Don't set error multiple times */ error = NULL; } else value->status = G_FILE_ATTRIBUTE_STATUS_SET; } #ifdef HAVE_UTIMES /* Group all time settings into one call * Change times as the last thing to avoid it changing due to metadata changes */ mtime = _g_file_info_get_attribute_value (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); mtime_usec = _g_file_info_get_attribute_value (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC); atime = _g_file_info_get_attribute_value (info, G_FILE_ATTRIBUTE_TIME_ACCESS); atime_usec = _g_file_info_get_attribute_value (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC); if (mtime || mtime_usec || atime || atime_usec) { if (!set_mtime_atime (filename, mtime, mtime_usec, atime, atime_usec, error)) { status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING; res = FALSE; /* Don't set error multiple times */ error = NULL; } else status = G_FILE_ATTRIBUTE_STATUS_SET; if (mtime) mtime->status = status; if (mtime_usec) mtime_usec->status = status; if (atime) atime->status = status; if (atime_usec) atime_usec->status = status; } #endif /* xattrs are handled by default callback */ /* SELinux context */ #ifdef HAVE_SELINUX if (is_selinux_enabled ()) { value = _g_file_info_get_attribute_value (info, G_FILE_ATTRIBUTE_SELINUX_CONTEXT); if (value) { if (!set_selinux_context (filename, value, error)) { value->status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING; res = FALSE; /* Don't set error multiple times */ error = NULL; } else value->status = G_FILE_ATTRIBUTE_STATUS_SET; } } #endif vfs = g_vfs_get_default (); class = G_VFS_GET_CLASS (vfs); if (class->local_file_set_attributes) { if (!class->local_file_set_attributes (vfs, filename, info, flags, cancellable, error)) { res = FALSE; /* Don't set error multiple times */ error = NULL; } } return res; }