f48f59ffd6f0b5b74cec31b18ec6c76321d6c46b
[platform/upstream/glib.git] / gio / glocalfileinfo.c
1 /* GIO - GLib Input, Output and Streaming Library
2  * 
3  * Copyright (C) 2006-2007 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  * Author: Alexander Larsson <alexl@redhat.com>
21  */
22
23 #include <config.h>
24
25 #include <sys/time.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <errno.h>
32 #ifdef HAVE_GRP_H
33 #include <grp.h>
34 #endif
35 #ifdef HAVE_PWD_H
36 #include <pwd.h>
37 #endif
38 #ifdef HAVE_SELINUX
39 #include <selinux/selinux.h>
40 #endif
41
42 #ifdef HAVE_XATTR
43
44 #if defined HAVE_SYS_XATTR_H
45   #include <sys/xattr.h>
46 #elif defined HAVE_ATTR_XATTR_H
47   #include <attr/xattr.h>
48 #else
49   #error "Neither <sys/xattr.h> nor <attr/xattr.h> is present but extended attribute support is enabled."
50 #endif /* defined HAVE_SYS_XATTR_H || HAVE_ATTR_XATTR_H */
51
52 #endif /* HAVE_XATTR */
53
54 #include <glib/gstdio.h>
55 #include <glib/gchecksum.h>
56
57 #include "glibintl.h"
58
59 #include "glocalfileinfo.h"
60 #include "gioerror.h"
61 #include "gthemedicon.h"
62 #include "gcontenttype.h"
63 #include "gcontenttypeprivate.h"
64
65 #include "gioalias.h"
66
67 struct ThumbMD5Context {
68         guint32 buf[4];
69         guint32 bits[2];
70         unsigned char in[64];
71 };
72
73 typedef struct {
74   char *user_name;
75   char *real_name;
76 } UidData;
77
78 G_LOCK_DEFINE_STATIC (uid_cache);
79 static GHashTable *uid_cache = NULL;
80
81 G_LOCK_DEFINE_STATIC (gid_cache);
82 static GHashTable *gid_cache = NULL;
83
84 char *
85 _g_local_file_info_create_etag (struct stat *statbuf)
86 {
87   GTimeVal tv;
88   
89   tv.tv_sec = statbuf->st_mtime;
90 #if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC)
91   tv.tv_usec = statbuf->st_mtimensec / 1000;
92 #elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
93   tv.tv_usec = statbuf->st_mtim.tv_nsec / 1000;
94 #else
95   tv.tv_usec = 0;
96 #endif
97
98   return g_strdup_printf ("%lu:%lu", tv.tv_sec, tv.tv_usec);
99 }
100
101 static char *
102 _g_local_file_info_create_file_id (struct stat *statbuf)
103 {
104   return g_strdup_printf ("l%" G_GUINT64_FORMAT ":%" G_GUINT64_FORMAT,
105                           (guint64) statbuf->st_dev, 
106                           (guint64) statbuf->st_ino);
107 }
108
109 static char *
110 _g_local_file_info_create_fs_id (struct stat *statbuf)
111 {
112   return g_strdup_printf ("l%" G_GUINT64_FORMAT,
113                           (guint64) statbuf->st_dev);
114 }
115
116
117 static gchar *
118 read_link (const gchar *full_name)
119 {
120 #ifdef HAVE_READLINK
121   gchar *buffer;
122   guint size;
123   
124   size = 256;
125   buffer = g_malloc (size);
126   
127   while (1)
128     {
129       int read_size;
130       
131       read_size = readlink (full_name, buffer, size);
132       if (read_size < 0)
133         {
134           g_free (buffer);
135           return NULL;
136         }
137       if (read_size < size)
138         {
139           buffer[read_size] = 0;
140           return buffer;
141         }
142       size *= 2;
143       buffer = g_realloc (buffer, size);
144     }
145 #else
146   return NULL;
147 #endif
148 }
149
150 /* Get the SELinux security context */
151 static void
152 get_selinux_context (const char            *path,
153                      GFileInfo             *info,
154                      GFileAttributeMatcher *attribute_matcher,
155                      gboolean               follow_symlinks)
156 {
157 #ifdef HAVE_SELINUX
158   char *context;
159
160   if (!g_file_attribute_matcher_matches (attribute_matcher, "selinux:context"))
161     return;
162   
163   if (is_selinux_enabled ())
164     {
165       if (follow_symlinks)
166         {
167           if (lgetfilecon_raw (path, &context) < 0)
168             return;
169         }
170       else
171         {
172           if (getfilecon_raw (path, &context) < 0)
173             return;
174         }
175
176       if (context)
177         {
178           g_file_info_set_attribute_string (info, "selinux:context", context);
179           freecon(context);
180         }
181     }
182 #endif
183 }
184
185 #ifdef HAVE_XATTR
186
187 /* Wrappers to hide away differences between (Linux) getxattr/lgetxattr and
188  * (Mac) getxattr(..., XATTR_NOFOLLOW)
189  */
190 #ifdef HAVE_XATTR_NOFOLLOW
191 #define g_fgetxattr(fd,name,value,size)  fgetxattr(fd,name,value,size,0,0)
192 #define g_flistxattr(fd,name,size)       flistxattr(fd,name,size,0)
193 #define g_setxattr(path,name,value,size) setxattr(path,name,value,size,0,0)
194 #else
195 #define g_fgetxattr     fgetxattr
196 #define g_flistxattr    flistxattr
197 #define g_setxattr(path,name,value,size) setxattr(path,name,value,size,0)
198 #endif
199
200 static ssize_t
201 g_getxattr (const char *path, const char *name, void *value, size_t size,
202             gboolean follow_symlinks)
203 {
204 #ifdef HAVE_XATTR_NOFOLLOW
205   return getxattr (path, name, value, size, 0, follow_symlinks ? 0 : XATTR_NOFOLLOW);
206 #else
207   if (follow_symlinks)
208     return getxattr (path, name, value, size);
209   else
210     return lgetxattr (path, name, value, size);
211 #endif
212 }
213
214 static ssize_t
215 g_listxattr(const char *path, char *namebuf, size_t size,
216             gboolean follow_symlinks)
217 {
218 #ifdef HAVE_XATTR_NOFOLLOW
219   return listxattr (path, namebuf, size, follow_symlinks ? 0 : XATTR_NOFOLLOW);
220 #else
221   if (follow_symlinks)
222     return listxattr (path, namebuf, size);
223   else
224     return llistxattr (path, namebuf, size);
225 #endif
226 }
227
228 static gboolean
229 valid_char (char c)
230 {
231   return c >= 32 && c <= 126 && c != '\\';
232 }
233
234 static gboolean
235 name_is_valid (const char *str)
236 {
237   while (*str)
238     {
239       if (!valid_char (*str++))
240         return FALSE;
241     }
242   return TRUE;
243 }
244
245 static char *
246 hex_escape_string (const char *str, 
247                    gboolean   *free_return)
248 {
249   int num_invalid, i;
250   char *escaped_str, *p;
251   unsigned char c;
252   static char *hex_digits = "0123456789abcdef";
253   int len;
254
255   len = strlen (str);
256   
257   num_invalid = 0;
258   for (i = 0; i < len; i++)
259     {
260       if (!valid_char (str[i]))
261         num_invalid++;
262     }
263
264   if (num_invalid == 0)
265     {
266       *free_return = FALSE;
267       return (char *)str;
268     }
269
270   escaped_str = g_malloc (len + num_invalid*3 + 1);
271
272   p = escaped_str;
273   for (i = 0; i < len; i++)
274     {
275       if (valid_char (str[i]))
276         *p++ = str[i];
277       else
278         {
279           c = str[i];
280           *p++ = '\\';
281           *p++ = 'x';
282           *p++ = hex_digits[(c >> 4) & 0xf];
283           *p++ = hex_digits[c & 0xf];
284         }
285     }
286   *p++ = 0;
287
288   *free_return = TRUE;
289   return escaped_str;
290 }
291
292 static char *
293 hex_unescape_string (const char *str, 
294                      int        *out_len, 
295                      gboolean   *free_return)
296 {
297   int i;
298   char *unescaped_str, *p;
299   unsigned char c;
300   int len;
301
302   len = strlen (str);
303   
304   if (strchr (str, '\\') == NULL)
305     {
306       if (out_len)
307         *out_len = len;
308       *free_return = FALSE;
309       return (char *)str;
310     }
311   
312   unescaped_str = g_malloc (len + 1);
313
314   p = unescaped_str;
315   for (i = 0; i < len; i++)
316     {
317       if (str[i] == '\\' &&
318           str[i+1] == 'x' &&
319           len - i >= 4)
320         {
321           c =
322             (g_ascii_xdigit_value (str[i+2]) << 4) |
323             g_ascii_xdigit_value (str[i+3]);
324           *p++ = c;
325           i += 3;
326         }
327       else
328         *p++ = str[i];
329     }
330   *p++ = 0;
331
332   if (out_len)
333     *out_len = p - unescaped_str;
334   *free_return = TRUE;
335   return unescaped_str;
336 }
337
338 static void
339 escape_xattr (GFileInfo  *info,
340               const char *gio_attr, /* gio attribute name */
341               const char *value, /* Is zero terminated */
342               size_t      len /* not including zero termination */)
343 {
344   char *escaped_val;
345   gboolean free_escaped_val;
346   
347   escaped_val = hex_escape_string (value, &free_escaped_val);
348   
349   g_file_info_set_attribute_string (info, gio_attr, escaped_val);
350   
351   if (free_escaped_val)
352     g_free (escaped_val);
353 }
354
355 static void
356 get_one_xattr (const char *path,
357                GFileInfo  *info,
358                const char *gio_attr,
359                const char *xattr,
360                gboolean    follow_symlinks)
361 {
362   char value[64];
363   char *value_p;
364   ssize_t len;
365
366   len = g_getxattr (path, xattr, value, sizeof (value)-1, follow_symlinks);
367
368   value_p = NULL;
369   if (len >= 0)
370     value_p = value;
371   else if (len == -1 && errno == ERANGE)
372     {
373       len = g_getxattr (path, xattr, NULL, 0, follow_symlinks);
374
375       if (len < 0)
376         return;
377
378       value_p = g_malloc (len+1);
379
380       len = g_getxattr (path, xattr, value_p, len, follow_symlinks);
381
382       if (len < 0)
383         {
384           g_free (value_p);
385           return;
386         }
387     }
388   else
389     return;
390   
391   /* Null terminate */
392   value_p[len] = 0;
393
394   escape_xattr (info, gio_attr, value_p, len);
395   
396   if (value_p != value)
397     g_free (value_p);
398 }
399
400 #endif /* defined HAVE_XATTR */
401
402 static void
403 get_xattrs (const char            *path,
404             gboolean               user,
405             GFileInfo             *info,
406             GFileAttributeMatcher *matcher,
407             gboolean               follow_symlinks)
408 {
409 #ifdef HAVE_XATTR
410   gboolean all;
411   gsize list_size;
412   ssize_t list_res_size;
413   size_t len;
414   char *list;
415   const char *attr, *attr2;
416
417   if (user)
418     all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr");
419   else
420     all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr_sys");
421
422   if (all)
423     {
424       list_res_size = g_listxattr (path, NULL, 0, follow_symlinks);
425
426       if (list_res_size == -1 ||
427           list_res_size == 0)
428         return;
429
430       list_size = list_res_size;
431       list = g_malloc (list_size);
432
433     retry:
434       
435       list_res_size = g_listxattr (path, list, list_size, follow_symlinks);
436       
437       if (list_res_size == -1 && errno == ERANGE)
438         {
439           list_size = list_size * 2;
440           list = g_realloc (list, list_size);
441           goto retry;
442         }
443
444       if (list_res_size == -1)
445         return;
446
447       attr = list;
448       while (list_res_size > 0)
449         {
450           if ((user && g_str_has_prefix (attr, "user.")) ||
451               (!user && !g_str_has_prefix (attr, "user.")))
452             {
453               char *escaped_attr, *gio_attr;
454               gboolean free_escaped_attr;
455               
456               if (user)
457                 {
458                   escaped_attr = hex_escape_string (attr + 5, &free_escaped_attr);
459                   gio_attr = g_strconcat ("xattr:", escaped_attr, NULL);
460                 }
461               else
462                 {
463                   escaped_attr = hex_escape_string (attr, &free_escaped_attr);
464                   gio_attr = g_strconcat ("xattr_sys:", escaped_attr, NULL);
465                 }
466               
467               if (free_escaped_attr)
468                 g_free (escaped_attr);
469               
470               get_one_xattr (path, info, gio_attr, attr, follow_symlinks);
471             }
472               
473           len = strlen (attr) + 1;
474           attr += len;
475           list_res_size -= len;
476         }
477
478       g_free (list);
479     }
480   else
481     {
482       while ((attr = g_file_attribute_matcher_enumerate_next (matcher)) != NULL)
483         {
484           char *unescaped_attribute, *a;
485           gboolean free_unescaped_attribute;
486
487           attr2 = strchr (attr, ':');
488           if (attr2)
489             {
490               attr2++; /* Skip ':' */
491               unescaped_attribute = hex_unescape_string (attr2, NULL, &free_unescaped_attribute);
492               if (user)
493                 a = g_strconcat ("user.", unescaped_attribute, NULL);
494               else
495                 a = unescaped_attribute;
496               
497               get_one_xattr (path, info, attr, a, follow_symlinks);
498
499               if (user)
500                 g_free (a);
501               
502               if (free_unescaped_attribute)
503                 g_free (unescaped_attribute);
504             }
505         }
506     }
507 #endif /* defined HAVE_XATTR */
508 }
509
510 #ifdef HAVE_XATTR
511 static void
512 get_one_xattr_from_fd (int         fd,
513                        GFileInfo  *info,
514                        const char *gio_attr,
515                        const char *xattr)
516 {
517   char value[64];
518   char *value_p;
519   ssize_t len;
520
521   len = g_fgetxattr (fd, xattr, value, sizeof (value)-1);
522
523   value_p = NULL;
524   if (len >= 0)
525     value_p = value;
526   else if (len == -1 && errno == ERANGE)
527     {
528       len = g_fgetxattr (fd, xattr, NULL, 0);
529
530       if (len < 0)
531         return;
532
533       value_p = g_malloc (len+1);
534
535       len = g_fgetxattr (fd, xattr, value_p, len);
536
537       if (len < 0)
538         {
539           g_free (value_p);
540           return;
541         }
542     }
543   else
544     return;
545   
546   /* Null terminate */
547   value_p[len] = 0;
548
549   escape_xattr (info, gio_attr, value_p, len);
550   
551   if (value_p != value)
552     g_free (value_p);
553 }
554 #endif /* defined HAVE_XATTR */
555
556 static void
557 get_xattrs_from_fd (int                    fd,
558                     gboolean               user,
559                     GFileInfo             *info,
560                     GFileAttributeMatcher *matcher)
561 {
562 #ifdef HAVE_XATTR
563   gboolean all;
564   gsize list_size;
565   ssize_t list_res_size;
566   size_t len;
567   char *list;
568   const char *attr, *attr2;
569
570   if (user)
571     all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr");
572   else
573     all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr_sys");
574
575   if (all)
576     {
577       list_res_size = g_flistxattr (fd, NULL, 0);
578
579       if (list_res_size == -1 ||
580           list_res_size == 0)
581         return;
582
583       list_size = list_res_size;
584       list = g_malloc (list_size);
585
586     retry:
587       
588       list_res_size = g_flistxattr (fd, list, list_size);
589       
590       if (list_res_size == -1 && errno == ERANGE)
591         {
592           list_size = list_size * 2;
593           list = g_realloc (list, list_size);
594           goto retry;
595         }
596
597       if (list_res_size == -1)
598         return;
599
600       attr = list;
601       while (list_res_size > 0)
602         {
603           if ((user && g_str_has_prefix (attr, "user.")) ||
604               (!user && !g_str_has_prefix (attr, "user.")))
605             {
606               char *escaped_attr, *gio_attr;
607               gboolean free_escaped_attr;
608               
609               if (user)
610                 {
611                   escaped_attr = hex_escape_string (attr + 5, &free_escaped_attr);
612                   gio_attr = g_strconcat ("xattr:", escaped_attr, NULL);
613                 }
614               else
615                 {
616                   escaped_attr = hex_escape_string (attr, &free_escaped_attr);
617                   gio_attr = g_strconcat ("xattr_sys:", escaped_attr, NULL);
618                 }
619               
620               if (free_escaped_attr)
621                 g_free (escaped_attr);
622               
623               get_one_xattr_from_fd (fd, info, gio_attr, attr);
624             }
625           
626           len = strlen (attr) + 1;
627           attr += len;
628           list_res_size -= len;
629         }
630
631       g_free (list);
632     }
633   else
634     {
635       while ((attr = g_file_attribute_matcher_enumerate_next (matcher)) != NULL)
636         {
637           char *unescaped_attribute, *a;
638           gboolean free_unescaped_attribute;
639
640           attr2 = strchr (attr, ':');
641           if (attr2)
642             {
643               attr2++; /* Skip ':' */
644               unescaped_attribute = hex_unescape_string (attr2, NULL, &free_unescaped_attribute);
645               if (user)
646                 a = g_strconcat ("user.", unescaped_attribute, NULL);
647               else
648                 a = unescaped_attribute;
649               
650               get_one_xattr_from_fd (fd, info, attr, a);
651
652               if (user)
653                 g_free (a);
654               
655               if (free_unescaped_attribute)
656                 g_free (unescaped_attribute);
657             }
658         }
659     }
660 #endif /* defined HAVE_XATTR */
661 }
662
663 #ifdef HAVE_XATTR
664 static gboolean
665 set_xattr (char                       *filename,
666            const char                 *escaped_attribute,
667            const GFileAttributeValue  *attr_value,
668            GError                    **error)
669 {
670   char *attribute, *value;
671   gboolean free_attribute, free_value;
672   int val_len, res, errsv;
673   gboolean is_user;
674   char *a;
675
676   if (attr_value == NULL)
677     {
678       g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
679                    _("Attribute value must be non-NULL"));
680       return FALSE;
681     }
682
683   if (attr_value->type != G_FILE_ATTRIBUTE_TYPE_STRING)
684     {
685       g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
686                    _("Invalid attribute type (string expected)"));
687       return FALSE;
688     }
689
690   if (!name_is_valid (escaped_attribute))
691     {
692       g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
693                    _("Invalid extended attribute name"));
694       return FALSE;
695     }
696
697   if (g_str_has_prefix (escaped_attribute, "xattr:"))
698     {
699       escaped_attribute += 6;
700       is_user = TRUE;
701     }
702   else
703     {
704       g_assert (g_str_has_prefix (escaped_attribute, "xattr_sys:"));
705       escaped_attribute += 10;
706       is_user = FALSE;
707     }
708   
709   attribute = hex_unescape_string (escaped_attribute, NULL, &free_attribute);
710   value = hex_unescape_string (attr_value->u.string, &val_len, &free_value);
711
712   if (is_user)
713     a = g_strconcat ("user.", attribute, NULL);
714   else
715     a = attribute;
716   
717   res = g_setxattr (filename, a, value, val_len);
718   errsv = errno;
719   
720   if (is_user)
721     g_free (a);
722   
723   if (free_attribute)
724     g_free (attribute);
725   
726   if (free_value)
727     g_free (value);
728
729   if (res == -1)
730     {
731       g_set_error (error, G_IO_ERROR,
732                    g_io_error_from_errno (errsv),
733                    _("Error setting extended attribute '%s': %s"),
734                    escaped_attribute, g_strerror (errno));
735       return FALSE;
736     }
737   
738   return TRUE;
739 }
740
741 #endif
742
743
744 void
745 _g_local_file_info_get_parent_info (const char            *dir,
746                                     GFileAttributeMatcher *attribute_matcher,
747                                     GLocalParentFileInfo  *parent_info)
748 {
749   struct stat statbuf;
750   int res;
751   
752   parent_info->writable = FALSE;
753   parent_info->is_sticky = FALSE;
754   parent_info->device = 0;
755
756   if (g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME) ||
757       g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE) ||
758       g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH) ||
759       g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT))
760     {
761       parent_info->writable = (g_access (dir, W_OK) == 0);
762       
763       res = g_stat (dir, &statbuf);
764
765       /*
766        * The sticky bit (S_ISVTX) on a directory means that a file in that directory can be
767        * renamed or deleted only by the owner of the file, by the owner of the directory, and
768        * by a privileged process.
769        */
770       if (res == 0)
771         {
772           parent_info->is_sticky = (statbuf.st_mode & S_ISVTX) != 0;
773           parent_info->owner = statbuf.st_uid;
774           parent_info->device = statbuf.st_dev;
775         }
776     }
777 }
778
779 static void
780 get_access_rights (GFileAttributeMatcher *attribute_matcher,
781                    GFileInfo             *info,
782                    const gchar           *path,
783                    struct stat           *statbuf,
784                    GLocalParentFileInfo  *parent_info)
785 {
786   if (g_file_attribute_matcher_matches (attribute_matcher,
787                                         G_FILE_ATTRIBUTE_ACCESS_CAN_READ))
788     g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
789                                        g_access (path, R_OK) == 0);
790   
791   if (g_file_attribute_matcher_matches (attribute_matcher,
792                                         G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
793     g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
794                                        g_access (path, W_OK) == 0);
795   
796   if (g_file_attribute_matcher_matches (attribute_matcher,
797                                         G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
798     g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
799                                        g_access (path, X_OK) == 0);
800
801
802   if (parent_info)
803     {
804       gboolean writable;
805
806       writable = FALSE;
807       if (parent_info->writable)
808         {
809           if (parent_info->is_sticky)
810             {
811               uid_t uid = geteuid ();
812
813               if (uid == statbuf->st_uid ||
814                   uid == parent_info->owner ||
815                   uid == 0)
816                 writable = TRUE;
817             }
818           else
819             writable = TRUE;
820         }
821
822       if (g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME))
823         g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME,
824                                            writable);
825       
826       if (g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE))
827         g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE,
828                                            writable);
829
830       /* TODO: This means we can move it, but we should also look for a trash dir */
831       if (g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH))
832         g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH,
833                                            writable);
834     }
835 }
836
837 static void
838 set_info_from_stat (GFileInfo             *info, 
839                     struct stat           *statbuf,
840                     GFileAttributeMatcher *attribute_matcher)
841 {
842   GFileType file_type;
843
844   file_type = G_FILE_TYPE_UNKNOWN;
845
846   if (S_ISREG (statbuf->st_mode))
847     file_type = G_FILE_TYPE_REGULAR;
848   else if (S_ISDIR (statbuf->st_mode))
849     file_type = G_FILE_TYPE_DIRECTORY;
850   else if (S_ISCHR (statbuf->st_mode) ||
851            S_ISBLK (statbuf->st_mode) ||
852            S_ISFIFO (statbuf->st_mode)
853 #ifdef S_ISSOCK
854            || S_ISSOCK (statbuf->st_mode)
855 #endif
856            )
857     file_type = G_FILE_TYPE_SPECIAL;
858 #ifdef S_ISLNK
859   else if (S_ISLNK (statbuf->st_mode))
860     file_type = G_FILE_TYPE_SYMBOLIC_LINK;
861 #endif
862
863   g_file_info_set_file_type (info, file_type);
864   g_file_info_set_size (info, statbuf->st_size);
865
866   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_DEVICE, statbuf->st_dev);
867   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE, statbuf->st_ino);
868   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, statbuf->st_mode);
869   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_NLINK, statbuf->st_nlink);
870   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, statbuf->st_uid);
871   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, statbuf->st_gid);
872   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_RDEV, statbuf->st_rdev);
873 #if defined (HAVE_STRUCT_STAT_BLKSIZE)
874   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE, statbuf->st_blksize);
875 #endif
876 #if defined (HAVE_STRUCT_STAT_BLOCKS)
877   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_BLOCKS, statbuf->st_blocks);
878 #endif
879   
880   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, statbuf->st_mtime);
881 #if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC)
882   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, statbuf->st_mtimensec / 1000);
883 #elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
884   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, statbuf->st_mtim.tv_nsec / 1000);
885 #endif
886   
887   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, statbuf->st_atime);
888 #if defined (HAVE_STRUCT_STAT_ST_ATIMENSEC)
889   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, statbuf->st_atimensec / 1000);
890 #elif defined (HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC)
891   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, statbuf->st_atim.tv_nsec / 1000);
892 #endif
893   
894   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED, statbuf->st_ctime);
895 #if defined (HAVE_STRUCT_STAT_ST_CTIMENSEC)
896   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, statbuf->st_ctimensec / 1000);
897 #elif defined (HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC)
898   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, statbuf->st_ctim.tv_nsec / 1000);
899 #endif
900
901   if (g_file_attribute_matcher_matches (attribute_matcher,
902                                         G_FILE_ATTRIBUTE_ETAG_VALUE))
903     {
904       char *etag = _g_local_file_info_create_etag (statbuf);
905       g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag);
906       g_free (etag);
907     }
908
909   if (g_file_attribute_matcher_matches (attribute_matcher,
910                                         G_FILE_ATTRIBUTE_ID_FILE))
911     {
912       char *id = _g_local_file_info_create_file_id (statbuf);
913       g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE, id);
914       g_free (id);
915     }
916
917   if (g_file_attribute_matcher_matches (attribute_matcher,
918                                         G_FILE_ATTRIBUTE_ID_FS))
919     {
920       char *id = _g_local_file_info_create_fs_id (statbuf);
921       g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ID_FS, id);
922       g_free (id);
923     }
924 }
925
926 static char *
927 make_valid_utf8 (const char *name)
928 {
929   GString *string;
930   const gchar *remainder, *invalid;
931   gint remaining_bytes, valid_bytes;
932   
933   string = NULL;
934   remainder = name;
935   remaining_bytes = strlen (name);
936   
937   while (remaining_bytes != 0) 
938     {
939       if (g_utf8_validate (remainder, remaining_bytes, &invalid)) 
940         break;
941       valid_bytes = invalid - remainder;
942     
943       if (string == NULL) 
944         string = g_string_sized_new (remaining_bytes);
945
946       g_string_append_len (string, remainder, valid_bytes);
947       /* append U+FFFD REPLACEMENT CHARACTER */
948       g_string_append (string, "\357\277\275");
949       
950       remaining_bytes -= valid_bytes + 1;
951       remainder = invalid + 1;
952     }
953   
954   if (string == NULL)
955     return g_strdup (name);
956   
957   g_string_append (string, remainder);
958
959   g_assert (g_utf8_validate (string->str, -1, NULL));
960   
961   return g_string_free (string, FALSE);
962 }
963
964 static char *
965 convert_pwd_string_to_utf8 (char *pwd_str)
966 {
967   char *utf8_string;
968   
969   if (!g_utf8_validate (pwd_str, -1, NULL))
970     {
971       utf8_string = g_locale_to_utf8 (pwd_str, -1, NULL, NULL, NULL);
972       if (utf8_string == NULL)
973         utf8_string = make_valid_utf8 (pwd_str);
974     }
975   else 
976     utf8_string = g_strdup (pwd_str);
977   
978   return utf8_string;
979 }
980
981 static void
982 uid_data_free (UidData *data)
983 {
984   g_free (data->user_name);
985   g_free (data->real_name);
986   g_free (data);
987 }
988
989 /* called with lock held */
990 static UidData *
991 lookup_uid_data (uid_t uid)
992 {
993   UidData *data;
994   char buffer[4096];
995   struct passwd pwbuf;
996   struct passwd *pwbufp;
997   char *gecos, *comma;
998   
999   if (uid_cache == NULL)
1000     uid_cache = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)uid_data_free);
1001
1002   data = g_hash_table_lookup (uid_cache, GINT_TO_POINTER (uid));
1003
1004   if (data)
1005     return data;
1006
1007   data = g_new0 (UidData, 1);
1008
1009 #if defined(HAVE_POSIX_GETPWUID_R)
1010   getpwuid_r (uid, &pwbuf, buffer, sizeof(buffer), &pwbufp);
1011 #elif defined(HAVE_NONPOSIX_GETPWUID_R)
1012   pwbufp = getpwuid_r (uid, &pwbuf, buffer, sizeof(buffer));
1013 #else
1014   pwbufp = getpwuid (uid);
1015 #endif
1016
1017   if (pwbufp != NULL)
1018     {
1019       if (pwbufp->pw_name != NULL && pwbufp->pw_name[0] != 0)
1020         data->user_name = convert_pwd_string_to_utf8 (pwbufp->pw_name);
1021
1022       gecos = pwbufp->pw_gecos;
1023
1024       if (gecos)
1025         {
1026           comma = strchr (gecos, ',');
1027           if (comma)
1028             *comma = 0;
1029           data->real_name = convert_pwd_string_to_utf8 (gecos);
1030         }
1031     }
1032
1033   /* Default fallbacks */
1034   if (data->real_name == NULL)
1035     {
1036       if (data->user_name != NULL)
1037         data->real_name = g_strdup (data->user_name);
1038       else
1039         data->real_name = g_strdup_printf("user #%d", (int)uid);
1040     }
1041   
1042   if (data->user_name == NULL)
1043     data->user_name = g_strdup_printf("%d", (int)uid);
1044   
1045   g_hash_table_replace (uid_cache, GINT_TO_POINTER (uid), data);
1046   
1047   return data;
1048 }
1049
1050 static char *
1051 get_username_from_uid (uid_t uid)
1052 {
1053   char *res;
1054   UidData *data;
1055   
1056   G_LOCK (uid_cache);
1057   data = lookup_uid_data (uid);
1058   res = g_strdup (data->user_name);  
1059   G_UNLOCK (uid_cache);
1060
1061   return res;
1062 }
1063
1064 static char *
1065 get_realname_from_uid (uid_t uid)
1066 {
1067   char *res;
1068   UidData *data;
1069   
1070   G_LOCK (uid_cache);
1071   data = lookup_uid_data (uid);
1072   res = g_strdup (data->real_name);  
1073   G_UNLOCK (uid_cache);
1074   
1075   return res;
1076 }
1077
1078
1079 /* called with lock held */
1080 static char *
1081 lookup_gid_name (gid_t gid)
1082 {
1083   char *name;
1084   char buffer[4096];
1085   struct group gbuf;
1086   struct group *gbufp;
1087   
1088   if (gid_cache == NULL)
1089     gid_cache = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
1090
1091   name = g_hash_table_lookup (gid_cache, GINT_TO_POINTER (gid));
1092
1093   if (name)
1094     return name;
1095
1096 #if defined (HAVE_POSIX_GETGRGID_R)
1097   getgrgid_r (gid, &gbuf, buffer, sizeof(buffer), &gbufp);
1098 #elif defined (HAVE_NONPOSIX_GETGRGID_R)
1099   gbufp = getgrgid_r (gid, &gbuf, buffer, sizeof(buffer));
1100 #else
1101   gbufp = getgrgid (gid);
1102 #endif
1103
1104   if (gbufp != NULL &&
1105       gbufp->gr_name != NULL &&
1106       gbufp->gr_name[0] != 0)
1107     name = convert_pwd_string_to_utf8 (gbufp->gr_name);
1108   else
1109     name = g_strdup_printf("%d", (int)gid);
1110   
1111   g_hash_table_replace (gid_cache, GINT_TO_POINTER (gid), name);
1112   
1113   return name;
1114 }
1115
1116 static char *
1117 get_groupname_from_gid (gid_t gid)
1118 {
1119   char *res;
1120   char *name;
1121   
1122   G_LOCK (gid_cache);
1123   name = lookup_gid_name (gid);
1124   res = g_strdup (name);  
1125   G_UNLOCK (gid_cache);
1126   return res;
1127 }
1128
1129 static char *
1130 get_content_type (const char          *basename,
1131                   const char          *path,
1132                   struct stat         *statbuf,
1133                   gboolean             is_symlink,
1134                   gboolean             symlink_broken,
1135                   GFileQueryInfoFlags  flags,
1136                   gboolean             fast)
1137 {
1138   if (is_symlink &&
1139       (symlink_broken || (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)))
1140     return g_strdup  ("inode/symlink");
1141   else if (S_ISDIR(statbuf->st_mode))
1142     return g_strdup ("inode/directory");
1143   else if (S_ISCHR(statbuf->st_mode))
1144     return g_strdup ("inode/chardevice");
1145   else if (S_ISBLK(statbuf->st_mode))
1146     return g_strdup ("inode/blockdevice");
1147   else if (S_ISFIFO(statbuf->st_mode))
1148     return g_strdup ("inode/fifo");
1149 #ifdef S_ISSOCK
1150   else if (S_ISSOCK(statbuf->st_mode))
1151     return g_strdup ("inode/socket");
1152 #endif
1153   else
1154     {
1155       char *content_type;
1156       gboolean result_uncertain;
1157       
1158       content_type = g_content_type_guess (basename, NULL, 0, &result_uncertain);
1159       
1160 #ifndef G_OS_WIN32
1161       if (!fast && result_uncertain && path != NULL)
1162         {
1163           guchar sniff_buffer[4096];
1164           gsize sniff_length;
1165           int fd;
1166
1167           sniff_length = _g_unix_content_type_get_sniff_len ();
1168           if (sniff_length > 4096)
1169             sniff_length = 4096;
1170           
1171           fd = open (path, O_RDONLY);
1172           if (fd != -1)
1173             {
1174               ssize_t res;
1175               
1176               res = read (fd, sniff_buffer, sniff_length);
1177               close (fd);
1178               if (res > 0)
1179                 {
1180                   g_free (content_type);
1181                   content_type = g_content_type_guess (basename, sniff_buffer, res, NULL);
1182                 }
1183             }
1184         }
1185 #endif
1186       
1187       return content_type;
1188     }
1189   
1190 }
1191
1192 static void
1193 get_thumbnail_attributes (const char *path,
1194                           GFileInfo  *info)
1195 {
1196   GChecksum *checksum;
1197   char *uri;
1198   char *filename;
1199   char *basename;
1200
1201   uri = g_filename_to_uri (path, NULL, NULL);
1202
1203   checksum = g_checksum_new (G_CHECKSUM_MD5);
1204   g_checksum_update (checksum, (const gchar *) uri, strlen (uri));
1205
1206   basename = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
1207   g_checksum_free (checksum);
1208
1209   filename = g_build_filename (g_get_home_dir(),
1210                                ".thumbnails", "normal", basename,
1211                                NULL);
1212
1213   if (g_file_test (filename, G_FILE_TEST_IS_REGULAR))
1214     g_file_info_set_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH, filename);
1215   else
1216     {
1217       g_free (filename);
1218       filename = g_build_filename (g_get_home_dir(),
1219                                    ".thumbnails", "fail",
1220                                    "gnome-thumbnail-factory",
1221                                    basename,
1222                                    NULL);
1223
1224       if (g_file_test (filename, G_FILE_TEST_IS_REGULAR))
1225         g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED, TRUE);
1226     }
1227   g_free (basename);
1228   g_free (filename);
1229 }
1230
1231
1232 GFileInfo *
1233 _g_local_file_info_get (const char             *basename,
1234                         const char             *path,
1235                         GFileAttributeMatcher  *attribute_matcher,
1236                         GFileQueryInfoFlags     flags,
1237                         GLocalParentFileInfo   *parent_info,
1238                         GError                **error)
1239 {
1240   GFileInfo *info;
1241   struct stat statbuf;
1242   struct stat statbuf2;
1243   int res;
1244   gboolean is_symlink, symlink_broken;
1245
1246   info = g_file_info_new ();
1247
1248   /* Make sure we don't set any unwanted attributes */
1249   g_file_info_set_attribute_mask (info, attribute_matcher);
1250   
1251   g_file_info_set_name (info, basename);
1252
1253   /* Avoid stat in trivial case */
1254   if (attribute_matcher == NULL)
1255     return info;
1256
1257   res = g_lstat (path, &statbuf);
1258   if (res == -1)
1259     {
1260       g_object_unref (info);
1261       g_set_error (error, G_IO_ERROR,
1262                    g_io_error_from_errno (errno),
1263                    _("Error stating file '%s': %s"),
1264                    path, g_strerror (errno));
1265       return NULL;
1266     }
1267   
1268 #ifdef S_ISLNK
1269   is_symlink = S_ISLNK (statbuf.st_mode);
1270 #else
1271   is_symlink = FALSE;
1272 #endif
1273   symlink_broken = FALSE;
1274   
1275   if (is_symlink)
1276     {
1277       g_file_info_set_is_symlink (info, TRUE);
1278
1279       /* Unless NOFOLLOW was set we default to following symlinks */
1280       if (!(flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
1281         {
1282           res = stat (path, &statbuf2);
1283
1284             /* Report broken links as symlinks */
1285           if (res != -1)
1286             statbuf = statbuf2;
1287           else
1288             symlink_broken = TRUE;
1289         }
1290     }
1291
1292   set_info_from_stat (info, &statbuf, attribute_matcher);
1293   
1294   if (basename != NULL && basename[0] == '.')
1295     g_file_info_set_is_hidden (info, TRUE);
1296
1297   if (basename != NULL && basename[strlen (basename) -1] == '~')
1298     g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STD_IS_BACKUP, TRUE);
1299
1300   if (is_symlink &&
1301       g_file_attribute_matcher_matches (attribute_matcher,
1302                                         G_FILE_ATTRIBUTE_STD_SYMLINK_TARGET))
1303     {
1304       char *link = read_link (path);
1305       g_file_info_set_symlink_target (info, link);
1306       g_free (link);
1307     }
1308
1309   if (g_file_attribute_matcher_matches (attribute_matcher,
1310                                         G_FILE_ATTRIBUTE_STD_DISPLAY_NAME))
1311     {
1312       char *display_name = g_filename_display_basename (path);
1313       
1314       if (strstr (display_name, "\357\277\275") != NULL)
1315         {
1316           char *p = display_name;
1317           display_name = g_strconcat (display_name, _(" (invalid encoding)"), NULL);
1318           g_free (p);
1319         }
1320       g_file_info_set_display_name (info, display_name);
1321       g_free (display_name);
1322     }
1323   
1324   if (g_file_attribute_matcher_matches (attribute_matcher,
1325                                         G_FILE_ATTRIBUTE_STD_EDIT_NAME))
1326     {
1327       char *edit_name = g_filename_display_basename (path);
1328       g_file_info_set_edit_name (info, edit_name);
1329       g_free (edit_name);
1330     }
1331
1332   
1333   if (g_file_attribute_matcher_matches (attribute_matcher,
1334                                         G_FILE_ATTRIBUTE_STD_COPY_NAME))
1335     {
1336       char *copy_name = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
1337       if (copy_name)
1338         g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STD_COPY_NAME, copy_name);
1339       g_free (copy_name);
1340     }
1341
1342   if (g_file_attribute_matcher_matches (attribute_matcher,
1343                                         G_FILE_ATTRIBUTE_STD_CONTENT_TYPE) ||
1344       g_file_attribute_matcher_matches (attribute_matcher,
1345                                         G_FILE_ATTRIBUTE_STD_ICON))
1346     {
1347       char *content_type = get_content_type (basename, path, &statbuf, is_symlink, symlink_broken, flags, FALSE);
1348
1349       if (content_type)
1350         {
1351           g_file_info_set_content_type (info, content_type);
1352
1353           if (g_file_attribute_matcher_matches (attribute_matcher,
1354                                                 G_FILE_ATTRIBUTE_STD_ICON))
1355             {
1356               char *mimetype_icon, *generic_mimetype_icon, *type_icon, *p;
1357               char *icon_names[3];
1358               GIcon *icon;
1359               int i;
1360
1361               mimetype_icon = g_strdup (content_type);
1362               
1363               while ((p = strchr(mimetype_icon, '/')) != NULL)
1364                 *p = '-';
1365
1366               p = strchr (content_type, '/');
1367               if (p == NULL)
1368                 p = content_type + strlen (content_type);
1369
1370               generic_mimetype_icon = g_malloc (p - content_type + strlen ("-x-generic") + 1);
1371               memcpy (generic_mimetype_icon, content_type, p - content_type);
1372               memcpy (generic_mimetype_icon + (p - content_type), "-x-generic", strlen ("-x-generic"));
1373               generic_mimetype_icon[(p - content_type) + strlen ("-x-generic")] = 0;
1374
1375               /* TODO: Special case desktop dir? That could be expensive with xdg dirs... */
1376               if (strcmp (path, g_get_home_dir ()) == 0)
1377                 type_icon = "user-home";
1378               else if (S_ISDIR (statbuf.st_mode)) 
1379                 type_icon = "folder";
1380               else if (statbuf.st_mode & S_IXUSR)
1381                 type_icon = "application-x-executable";
1382               else
1383                 type_icon = "text-x-generic";
1384
1385               i = 0;
1386               icon_names[i++] = mimetype_icon;
1387               icon_names[i++] = generic_mimetype_icon;
1388               if (strcmp (generic_mimetype_icon, type_icon) != 0 &&
1389                   strcmp (mimetype_icon, type_icon) != 0) 
1390                 icon_names[i++] = type_icon;
1391               
1392               icon = g_themed_icon_new_from_names (icon_names, i);
1393               g_file_info_set_icon (info, icon);
1394               
1395               g_object_unref (icon);
1396               g_free (mimetype_icon);
1397               g_free (generic_mimetype_icon);
1398             }
1399           
1400           g_free (content_type);
1401         }
1402     }
1403
1404   if (g_file_attribute_matcher_matches (attribute_matcher,
1405                                         G_FILE_ATTRIBUTE_STD_FAST_CONTENT_TYPE))
1406     {
1407       char *content_type = get_content_type (basename, path, &statbuf, is_symlink, symlink_broken, flags, TRUE);
1408       
1409       if (content_type)
1410         {
1411           g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STD_FAST_CONTENT_TYPE, content_type);
1412           g_free (content_type);
1413         }
1414     }
1415
1416   if (g_file_attribute_matcher_matches (attribute_matcher,
1417                                         G_FILE_ATTRIBUTE_OWNER_USER))
1418     {
1419       char *name;
1420       
1421       name = get_username_from_uid (statbuf.st_uid);
1422       if (name)
1423         g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER, name);
1424       g_free (name);
1425     }
1426
1427   if (g_file_attribute_matcher_matches (attribute_matcher,
1428                                         G_FILE_ATTRIBUTE_OWNER_USER_REAL))
1429     {
1430       char *name;
1431       
1432       name = get_realname_from_uid (statbuf.st_uid);
1433       if (name)
1434         g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER_REAL, name);
1435       g_free (name);
1436     }
1437   
1438   if (g_file_attribute_matcher_matches (attribute_matcher,
1439                                         G_FILE_ATTRIBUTE_OWNER_GROUP))
1440     {
1441       char *name;
1442       
1443       name = get_groupname_from_gid (statbuf.st_gid);
1444       if (name)
1445         g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP, name);
1446       g_free (name);
1447     }
1448
1449   if (parent_info && parent_info->device != 0 &&
1450       g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT) &&
1451       statbuf.st_dev != parent_info->device) 
1452     g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT, TRUE);
1453   
1454   get_access_rights (attribute_matcher, info, path, &statbuf, parent_info);
1455   
1456   get_selinux_context (path, info, attribute_matcher, (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) == 0);
1457   get_xattrs (path, TRUE, info, attribute_matcher, (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) == 0);
1458   get_xattrs (path, FALSE, info, attribute_matcher, (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) == 0);
1459
1460   if (g_file_attribute_matcher_matches (attribute_matcher,
1461                                         G_FILE_ATTRIBUTE_THUMBNAIL_PATH))
1462     get_thumbnail_attributes (path, info);
1463   
1464   g_file_info_unset_attribute_mask (info);
1465
1466   return info;
1467 }
1468
1469 GFileInfo *
1470 _g_local_file_info_get_from_fd (int      fd,
1471                                 char    *attributes,
1472                                 GError **error)
1473 {
1474   struct stat stat_buf;
1475   GFileAttributeMatcher *matcher;
1476   GFileInfo *info;
1477   
1478   if (fstat (fd, &stat_buf) == -1)
1479     {
1480       g_set_error (error, G_IO_ERROR,
1481                    g_io_error_from_errno (errno),
1482                    _("Error stating file descriptor: %s"),
1483                    g_strerror (errno));
1484       return NULL;
1485     }
1486
1487   info = g_file_info_new ();
1488
1489   matcher = g_file_attribute_matcher_new (attributes);
1490
1491   /* Make sure we don't set any unwanted attributes */
1492   g_file_info_set_attribute_mask (info, matcher);
1493   
1494   set_info_from_stat (info, &stat_buf, matcher);
1495   
1496 #ifdef HAVE_SELINUX
1497   if (g_file_attribute_matcher_matches (matcher, "selinux:context") &&
1498       is_selinux_enabled ())
1499     {
1500       char *context;
1501       if (fgetfilecon_raw (fd, &context) >= 0)
1502         {
1503           g_file_info_set_attribute_string (info, "selinux:context", context);
1504           freecon(context);
1505         }
1506     }
1507 #endif
1508
1509   get_xattrs_from_fd (fd, TRUE, info, matcher);
1510   get_xattrs_from_fd (fd, FALSE, info, matcher);
1511   
1512   g_file_attribute_matcher_unref (matcher);
1513
1514   g_file_info_unset_attribute_mask (info);
1515   
1516   return info;
1517 }
1518
1519 static gboolean
1520 get_uint32 (const GFileAttributeValue  *value,
1521             guint32                    *val_out,
1522             GError                    **error)
1523 {
1524   if (value->type != G_FILE_ATTRIBUTE_TYPE_UINT32)
1525     {
1526       g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1527                    _("Invalid attribute type (uint32 expected)"));
1528       return FALSE;
1529     }
1530
1531   *val_out = value->u.uint32;
1532   
1533   return TRUE;
1534 }
1535
1536 static gboolean
1537 get_uint64 (const GFileAttributeValue  *value,
1538             guint64                    *val_out,
1539             GError                    **error)
1540 {
1541   if (value->type != G_FILE_ATTRIBUTE_TYPE_UINT64)
1542     {
1543       g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1544                    _("Invalid attribute type (uint64 expected)"));
1545       return FALSE;
1546     }
1547
1548   *val_out = value->u.uint64;
1549   
1550   return TRUE;
1551 }
1552
1553 #if defined(HAVE_SYMLINK)
1554 static gboolean
1555 get_byte_string (const GFileAttributeValue  *value,
1556                  const char                **val_out,
1557                  GError                    **error)
1558 {
1559   if (value->type != G_FILE_ATTRIBUTE_TYPE_BYTE_STRING)
1560     {
1561       g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1562                    _("Invalid attribute type (byte string expected)"));
1563       return FALSE;
1564     }
1565
1566   *val_out = value->u.string;
1567   
1568   return TRUE;
1569 }
1570 #endif
1571
1572 static gboolean
1573 set_unix_mode (char                       *filename,
1574                const GFileAttributeValue  *value,
1575                GError                    **error)
1576 {
1577   guint32 val;
1578   
1579   if (!get_uint32 (value, &val, error))
1580     return FALSE;
1581   
1582   if (g_chmod (filename, val) == -1)
1583     {
1584       g_set_error (error, G_IO_ERROR,
1585                    g_io_error_from_errno (errno),
1586                    _("Error setting permissions: %s"),
1587                    g_strerror (errno));
1588       return FALSE;
1589     }
1590   return TRUE;
1591 }
1592
1593 #ifdef HAVE_CHOWN
1594 static gboolean
1595 set_unix_uid_gid (char                       *filename,
1596                   const GFileAttributeValue  *uid_value,
1597                   const GFileAttributeValue  *gid_value,
1598                   GFileQueryInfoFlags         flags,
1599                   GError                    **error)
1600 {
1601   int res;
1602   guint32 val;
1603   uid_t uid;
1604   gid_t gid;
1605   
1606   if (uid_value)
1607     {
1608       if (!get_uint32 (uid_value, &val, error))
1609         return FALSE;
1610       uid = val;
1611     }
1612   else
1613     uid = -1;
1614   
1615   if (gid_value)
1616     {
1617       if (!get_uint32 (gid_value, &val, error))
1618         return FALSE;
1619       gid = val;
1620     }
1621   else
1622     gid = -1;
1623   
1624   if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
1625     res = lchown (filename, uid, gid);
1626   else
1627     res = chown (filename, uid, gid);
1628   
1629   if (res == -1)
1630     {
1631       g_set_error (error, G_IO_ERROR,
1632                    g_io_error_from_errno (errno),
1633                    _("Error setting owner: %s"),
1634                    g_strerror (errno));
1635           return FALSE;
1636     }
1637   return TRUE;
1638 }
1639 #endif
1640
1641 #ifdef HAVE_SYMLINK
1642 static gboolean
1643 set_symlink (char                       *filename,
1644              const GFileAttributeValue  *value,
1645              GError                    **error)
1646 {
1647   const char *val;
1648   struct stat statbuf;
1649   
1650   if (!get_byte_string (value, &val, error))
1651     return FALSE;
1652   
1653   if (val == NULL)
1654     {
1655       g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1656                    _("symlink must be non-NULL"));
1657       return FALSE;
1658     }
1659   
1660   if (g_lstat (filename, &statbuf))
1661     {
1662       g_set_error (error, G_IO_ERROR,
1663                    g_io_error_from_errno (errno),
1664                    _("Error setting symlink: %s"),
1665                    g_strerror (errno));
1666       return FALSE;
1667     }
1668   
1669   if (!S_ISLNK (statbuf.st_mode))
1670     {
1671       g_set_error (error, G_IO_ERROR,
1672                    G_IO_ERROR_NOT_SYMBOLIC_LINK,
1673                    _("Error setting symlink: file is not a symlink"));
1674       return FALSE;
1675     }
1676   
1677   if (g_unlink (filename))
1678     {
1679       g_set_error (error, G_IO_ERROR,
1680                    g_io_error_from_errno (errno),
1681                    _("Error setting symlink: %s"),
1682                    g_strerror (errno));
1683       return FALSE;
1684     }
1685   
1686   if (symlink (filename, val) != 0)
1687     {
1688       g_set_error (error, G_IO_ERROR,
1689                    g_io_error_from_errno (errno),
1690                    _("Error setting symlink: %s"),
1691                    g_strerror (errno));
1692       return FALSE;
1693     }
1694   
1695   return TRUE;
1696 }
1697 #endif
1698
1699 static int
1700 lazy_stat (char        *filename, 
1701            struct stat *statbuf, 
1702            gboolean    *called_stat)
1703 {
1704   int res;
1705
1706   if (*called_stat)
1707     return 0;
1708   
1709   res = g_stat (filename, statbuf);
1710   
1711   if (res == 0)
1712     *called_stat = TRUE;
1713   
1714   return res;
1715 }
1716
1717
1718 #ifdef HAVE_UTIMES
1719 static gboolean
1720 set_mtime_atime (char                       *filename,
1721                  const GFileAttributeValue  *mtime_value,
1722                  const GFileAttributeValue  *mtime_usec_value,
1723                  const GFileAttributeValue  *atime_value,
1724                  const GFileAttributeValue  *atime_usec_value,
1725                  GError                    **error)
1726 {
1727   int res;
1728   guint64 val;
1729   guint32 val_usec;
1730   struct stat statbuf;
1731   gboolean got_stat = FALSE;
1732   struct timeval times[2] = { {0, 0}, {0, 0} };
1733
1734   /* ATIME */
1735   if (atime_value)
1736     {
1737       if (!get_uint64 (atime_value, &val, error))
1738         return FALSE;
1739       times[0].tv_sec = val;
1740     }
1741   else
1742     {
1743       if (lazy_stat (filename, &statbuf, &got_stat) == 0)
1744         {
1745           times[0].tv_sec = statbuf.st_mtime;
1746 #if defined (HAVE_STRUCT_STAT_ST_ATIMENSEC)
1747           times[0].tv_usec = statbuf.st_atimensec / 1000;
1748 #elif defined (HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC)
1749           times[0].tv_usec = statbuf.st_atim.tv_nsec / 1000;
1750 #endif
1751         }
1752     }
1753   
1754   if (atime_usec_value)
1755     {
1756       if (!get_uint32 (atime_usec_value, &val_usec, error))
1757         return FALSE;
1758       times[0].tv_usec = val_usec;
1759     }
1760
1761   /* MTIME */
1762   if (mtime_value)
1763     {
1764       if (!get_uint64 (mtime_value, &val, error))
1765         return FALSE;
1766       times[1].tv_sec = val;
1767     }
1768   else
1769     {
1770       if (lazy_stat (filename, &statbuf, &got_stat) == 0)
1771         {
1772           times[1].tv_sec = statbuf.st_mtime;
1773 #if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC)
1774           times[1].tv_usec = statbuf.st_mtimensec / 1000;
1775 #elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
1776           times[1].tv_usec = statbuf.st_mtim.tv_nsec / 1000;
1777 #endif
1778         }
1779     }
1780   
1781   if (mtime_usec_value)
1782     {
1783       if (!get_uint32 (mtime_usec_value, &val_usec, error))
1784         return FALSE;
1785       times[1].tv_usec = val_usec;
1786     }
1787   
1788   res = utimes(filename, times);
1789   if (res == -1)
1790     {
1791       g_set_error (error, G_IO_ERROR,
1792                    g_io_error_from_errno (errno),
1793                    _("Error setting owner: %s"),
1794                    g_strerror (errno));
1795           return FALSE;
1796     }
1797   return TRUE;
1798 }
1799 #endif
1800
1801 gboolean
1802 _g_local_file_info_set_attribute (char                       *filename,
1803                                   const char                 *attribute,
1804                                   const GFileAttributeValue  *value,
1805                                   GFileQueryInfoFlags         flags,
1806                                   GCancellable               *cancellable,
1807                                   GError                    **error)
1808 {
1809   if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) == 0)
1810     return set_unix_mode (filename, value, error);
1811   
1812 #ifdef HAVE_CHOWN
1813   else if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_UID) == 0)
1814     return set_unix_uid_gid (filename, value, NULL, flags, error);
1815   else if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_GID) == 0)
1816     return set_unix_uid_gid (filename, NULL, value, flags, error);
1817 #endif
1818   
1819 #ifdef HAVE_SYMLINK
1820   else if (strcmp (attribute, G_FILE_ATTRIBUTE_STD_SYMLINK_TARGET) == 0)
1821     return set_symlink (filename, value, error);
1822 #endif
1823
1824 #ifdef HAVE_UTIMES
1825   else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED) == 0)
1826     return set_mtime_atime (filename, value, NULL, NULL, NULL, error);
1827   else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC) == 0)
1828     return set_mtime_atime (filename, NULL, value, NULL, NULL, error);
1829   else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS) == 0)
1830     return set_mtime_atime (filename, NULL, NULL, value, NULL, error);
1831   else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC) == 0)
1832     return set_mtime_atime (filename, NULL, NULL, NULL, value, error);
1833 #endif
1834
1835 #ifdef HAVE_XATTR
1836   else if (g_str_has_prefix (attribute, "xattr:"))
1837     return set_xattr (filename, attribute, value, error);
1838   else if (g_str_has_prefix (attribute, "xattr_sys:"))
1839     return set_xattr (filename, attribute, value, error);
1840 #endif
1841   
1842   g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1843                _("Setting attribute %s not supported"), attribute);
1844   return FALSE;
1845 }
1846
1847 gboolean
1848 _g_local_file_info_set_attributes  (char                 *filename,
1849                                     GFileInfo            *info,
1850                                     GFileQueryInfoFlags   flags,
1851                                     GCancellable         *cancellable,
1852                                     GError              **error)
1853 {
1854   GFileAttributeValue *value, *uid, *gid;
1855   GFileAttributeValue *mtime, *mtime_usec, *atime, *atime_usec;
1856   GFileAttributeStatus status;
1857   gboolean res;
1858   
1859   /* Handles setting multiple specified data in a single set, and takes care
1860      of ordering restrictions when setting attributes */
1861
1862   res = TRUE;
1863
1864   /* Set symlink first, since this recreates the file */
1865 #ifdef HAVE_SYMLINK
1866   value = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_STD_SYMLINK_TARGET);
1867   if (value)
1868     {
1869       if (!set_symlink (filename, value, error))
1870         {
1871           value->status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING;
1872           res = FALSE;
1873           /* Don't set error multiple times */
1874           error = NULL;
1875         }
1876       else
1877         value->status = G_FILE_ATTRIBUTE_STATUS_SET;
1878         
1879     }
1880 #endif
1881
1882 #ifdef HAVE_CHOWN
1883   /* Group uid and gid setting into one call
1884    * Change ownership before permissions, since ownership changes can
1885      change permissions (e.g. setuid)
1886    */
1887   uid = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID);
1888   gid = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_UNIX_GID);
1889   
1890   if (uid || gid)
1891     {
1892       if (!set_unix_uid_gid (filename, uid, gid, flags, error))
1893         {
1894           status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING;
1895           res = FALSE;
1896           /* Don't set error multiple times */
1897           error = NULL;
1898         }
1899       else
1900         status = G_FILE_ATTRIBUTE_STATUS_SET;
1901       if (uid)
1902         uid->status = status;
1903       if (gid)
1904         gid->status = status;
1905     }
1906 #endif
1907   
1908   value = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE);
1909   if (value)
1910     {
1911       if (!set_unix_mode (filename, value, error))
1912         {
1913           value->status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING;
1914           res = FALSE;
1915           /* Don't set error multiple times */
1916           error = NULL;
1917         }
1918       else
1919         value->status = G_FILE_ATTRIBUTE_STATUS_SET;
1920         
1921     }
1922
1923 #ifdef HAVE_UTIMES
1924   /* Group all time settings into one call
1925    * Change times as the last thing to avoid it changing due to metadata changes
1926    */
1927   
1928   mtime = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
1929   mtime_usec = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC);
1930   atime = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
1931   atime_usec = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC);
1932
1933   if (mtime || mtime_usec || atime || atime_usec)
1934     {
1935       if (!set_mtime_atime (filename, mtime, mtime_usec, atime, atime_usec, error))
1936         {
1937           status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING;
1938           res = FALSE;
1939           /* Don't set error multiple times */
1940           error = NULL;
1941         }
1942       else
1943         status = G_FILE_ATTRIBUTE_STATUS_SET;
1944       
1945       if (mtime)
1946         mtime->status = status;
1947       if (mtime_usec)
1948         mtime_usec->status = status;
1949       if (atime)
1950         atime->status = status;
1951       if (atime_usec)
1952         atime_usec->status = status;
1953     }
1954 #endif
1955
1956   /* xattrs are handled by default callback */
1957
1958   return res;
1959 }