localfileenumerator: Improve non-stat'ing code
[platform/upstream/glib.git] / gio / glocalfileenumerator.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 <glib.h>
26 #include <glocalfileenumerator.h>
27 #include <glocalfileinfo.h>
28 #include <glocalfile.h>
29 #include <gioerror.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include "glibintl.h"
33
34
35 #define CHUNK_SIZE 1000
36
37 #ifdef G_OS_WIN32
38 #define USE_GDIR
39 #endif
40
41 #ifndef USE_GDIR
42
43 #include <sys/types.h>
44 #include <dirent.h>
45 #include <errno.h>
46
47 typedef struct {
48   char *name;
49   long inode;
50   GFileType type;
51 } DirEntry;
52
53 #endif
54
55 struct _GLocalFileEnumerator
56 {
57   GFileEnumerator parent;
58
59   GFileAttributeMatcher *matcher;
60   GFileAttributeMatcher *reduced_matcher;
61   char *filename;
62   char *attributes;
63   GFileQueryInfoFlags flags;
64
65   gboolean got_parent_info;
66   GLocalParentFileInfo parent_info;
67   
68 #ifdef USE_GDIR
69   GDir *dir;
70 #else
71   DIR *dir;
72   DirEntry *entries;
73   int entries_pos;
74   gboolean at_end;
75 #endif
76   
77   gboolean follow_symlinks;
78 };
79
80 #define g_local_file_enumerator_get_type _g_local_file_enumerator_get_type
81 G_DEFINE_TYPE (GLocalFileEnumerator, g_local_file_enumerator, G_TYPE_FILE_ENUMERATOR);
82
83 static GFileInfo *g_local_file_enumerator_next_file (GFileEnumerator  *enumerator,
84                                                      GCancellable     *cancellable,
85                                                      GError          **error);
86 static gboolean   g_local_file_enumerator_close     (GFileEnumerator  *enumerator,
87                                                      GCancellable     *cancellable,
88                                                      GError          **error);
89
90
91 static void
92 free_entries (GLocalFileEnumerator *local)
93 {
94 #ifndef USE_GDIR
95   int i;
96
97   if (local->entries != NULL)
98     {
99       for (i = 0; local->entries[i].name != NULL; i++)
100         g_free (local->entries[i].name);
101       
102       g_free (local->entries);
103     }
104 #endif
105 }
106
107 static void
108 g_local_file_enumerator_finalize (GObject *object)
109 {
110   GLocalFileEnumerator *local;
111
112   local = G_LOCAL_FILE_ENUMERATOR (object);
113
114   if (local->got_parent_info)
115     _g_local_file_info_free_parent_info (&local->parent_info);
116   g_free (local->filename);
117   g_file_attribute_matcher_unref (local->matcher);
118   g_file_attribute_matcher_unref (local->reduced_matcher);
119   if (local->dir)
120     {
121 #ifdef USE_GDIR
122       g_dir_close (local->dir);
123 #else
124       closedir (local->dir);
125 #endif      
126       local->dir = NULL;
127     }
128
129   free_entries (local);
130
131   G_OBJECT_CLASS (g_local_file_enumerator_parent_class)->finalize (object);
132 }
133
134
135 static void
136 g_local_file_enumerator_class_init (GLocalFileEnumeratorClass *klass)
137 {
138   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
139   GFileEnumeratorClass *enumerator_class = G_FILE_ENUMERATOR_CLASS (klass);
140   
141   gobject_class->finalize = g_local_file_enumerator_finalize;
142
143   enumerator_class->next_file = g_local_file_enumerator_next_file;
144   enumerator_class->close_fn = g_local_file_enumerator_close;
145 }
146
147 static void
148 g_local_file_enumerator_init (GLocalFileEnumerator *local)
149 {
150 }
151
152 #ifdef USE_GDIR
153 static void
154 convert_file_to_io_error (GError **error,
155                           GError  *file_error)
156 {
157   int new_code;
158
159   if (file_error == NULL)
160     return;
161   
162   new_code = G_IO_ERROR_FAILED;
163   
164   if (file_error->domain == G_FILE_ERROR) 
165     {
166       switch (file_error->code) 
167         {
168         case G_FILE_ERROR_NOENT:
169           new_code = G_IO_ERROR_NOT_FOUND;
170           break;
171         case G_FILE_ERROR_ACCES:
172           new_code = G_IO_ERROR_PERMISSION_DENIED;
173           break;
174         case G_FILE_ERROR_NOTDIR:
175           new_code = G_IO_ERROR_NOT_DIRECTORY;
176           break;
177         case G_FILE_ERROR_MFILE:
178           new_code = G_IO_ERROR_TOO_MANY_OPEN_FILES;
179           break;
180         default:
181           break;
182         }
183     }
184   
185   g_set_error_literal (error, G_IO_ERROR,
186                        new_code,
187                        file_error->message);
188 }
189 #else
190 static GFileAttributeMatcher *
191 g_file_attribute_matcher_subtract_attributes (GFileAttributeMatcher *matcher,
192                                               const char *           attributes)
193 {
194   GFileAttributeMatcher *result, *tmp;
195
196   tmp = g_file_attribute_matcher_new (attributes);
197   result = g_file_attribute_matcher_subtract (matcher, tmp);
198   g_file_attribute_matcher_unref (tmp);
199
200   return result;
201 }
202 #endif
203
204 GFileEnumerator *
205 _g_local_file_enumerator_new (GLocalFile *file,
206                               const char           *attributes,
207                               GFileQueryInfoFlags   flags,
208                               GCancellable         *cancellable,
209                               GError              **error)
210 {
211   GLocalFileEnumerator *local;
212   char *filename = g_file_get_path (G_FILE (file));
213
214 #ifdef USE_GDIR
215   GError *dir_error;
216   GDir *dir;
217   
218   dir_error = NULL;
219   dir = g_dir_open (filename, 0, error != NULL ? &dir_error : NULL);
220   if (dir == NULL) 
221     {
222       if (error != NULL)
223         {
224           convert_file_to_io_error (error, dir_error);
225           g_error_free (dir_error);
226         }
227       g_free (filename);
228       return NULL;
229     }
230 #else
231   DIR *dir;
232   int errsv;
233
234   dir = opendir (filename);
235   if (dir == NULL)
236     {
237       errsv = errno;
238
239       g_set_error_literal (error, G_IO_ERROR,
240                            g_io_error_from_errno (errsv),
241                            g_strerror (errsv));
242       g_free (filename);
243       return NULL;
244     }
245
246 #endif
247   
248   local = g_object_new (G_TYPE_LOCAL_FILE_ENUMERATOR,
249                         "container", file,
250                         NULL);
251
252   local->dir = dir;
253   local->filename = filename;
254   local->matcher = g_file_attribute_matcher_new (attributes);
255 #ifndef USE_GDIR
256   local->reduced_matcher = g_file_attribute_matcher_subtract_attributes (local->matcher,
257                                                                          G_LOCAL_FILE_INFO_NOSTAT_ATTRIBUTES","
258                                                                          "standard::type");
259 #endif
260   local->flags = flags;
261   
262   return G_FILE_ENUMERATOR (local);
263 }
264
265 #ifndef USE_GDIR
266 static int
267 sort_by_inode (const void *_a, const void *_b)
268 {
269   const DirEntry *a, *b;
270
271   a = _a;
272   b = _b;
273   return a->inode - b->inode;
274 }
275
276 #ifdef HAVE_STRUCT_DIRENT_D_TYPE
277 static GFileType
278 file_type_from_dirent (char d_type)
279 {
280   switch (d_type)
281     {
282     case DT_BLK:
283     case DT_CHR:
284     case DT_FIFO:
285     case DT_SOCK:
286       return G_FILE_TYPE_SPECIAL;
287     case DT_DIR:
288       return G_FILE_TYPE_DIRECTORY;
289     case DT_LNK:
290       return G_FILE_TYPE_SYMBOLIC_LINK;
291     case DT_REG:
292       return G_FILE_TYPE_REGULAR;
293     case DT_UNKNOWN:
294     default:
295       return G_FILE_TYPE_UNKNOWN;
296     }
297 }
298 #endif
299
300 static const char *
301 next_file_helper (GLocalFileEnumerator *local, GFileType *file_type)
302 {
303   struct dirent *entry;
304   const char *filename;
305   int i;
306
307   if (local->at_end)
308     return NULL;
309   
310   if (local->entries == NULL ||
311       (local->entries[local->entries_pos].name == NULL))
312     {
313       if (local->entries == NULL)
314         local->entries = g_new (DirEntry, CHUNK_SIZE + 1);
315       else
316         {
317           /* Restart by clearing old names */
318           for (i = 0; local->entries[i].name != NULL; i++)
319             g_free (local->entries[i].name);
320         }
321       
322       for (i = 0; i < CHUNK_SIZE; i++)
323         {
324           entry = readdir (local->dir);
325           while (entry 
326                  && (0 == strcmp (entry->d_name, ".") ||
327                      0 == strcmp (entry->d_name, "..")))
328             entry = readdir (local->dir);
329
330           if (entry)
331             {
332               local->entries[i].name = g_strdup (entry->d_name);
333               local->entries[i].inode = entry->d_ino;
334 #if HAVE_STRUCT_DIRENT_D_TYPE
335               local->entries[i].type = file_type_from_dirent (entry->d_type);
336 #else
337               local->entries[i].type = G_FILE_TYPE_UNKNOWN;
338 #endif
339             }
340           else
341             break;
342         }
343       local->entries[i].name = NULL;
344       local->entries_pos = 0;
345       
346       qsort (local->entries, i, sizeof (DirEntry), sort_by_inode);
347     }
348
349   filename = local->entries[local->entries_pos].name;
350   if (filename == NULL)
351     local->at_end = TRUE;
352     
353   *file_type = local->entries[local->entries_pos].type;
354
355   local->entries_pos++;
356
357   return filename;
358 }
359
360 #endif
361
362 static GFileInfo *
363 g_local_file_enumerator_next_file (GFileEnumerator  *enumerator,
364                                    GCancellable     *cancellable,
365                                    GError          **error)
366 {
367   GLocalFileEnumerator *local = G_LOCAL_FILE_ENUMERATOR (enumerator);
368   const char *filename;
369   char *path;
370   GFileInfo *info;
371   GError *my_error;
372   GFileType file_type;
373
374   if (!local->got_parent_info)
375     {
376       _g_local_file_info_get_parent_info (local->filename, local->matcher, &local->parent_info);
377       local->got_parent_info = TRUE;
378     }
379
380  next_file:
381
382 #ifdef USE_GDIR
383   filename = g_dir_read_name (local->dir);
384   file_type = G_FILE_TYPE_UNKNOWN;
385 #else
386   filename = next_file_helper (local, &file_type);
387 #endif
388
389   if (filename == NULL)
390     return NULL;
391
392   my_error = NULL;
393   path = g_build_filename (local->filename, filename, NULL);
394   if (file_type == G_FILE_TYPE_UNKNOWN ||
395       (file_type == G_FILE_TYPE_SYMBOLIC_LINK && !(local->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)))
396     {
397       info = _g_local_file_info_get (filename, path,
398                                      local->matcher,
399                                      local->flags,
400                                      &local->parent_info,
401                                      &my_error); 
402     }
403   else
404     {
405       info = _g_local_file_info_get (filename, path,
406                                      local->reduced_matcher,
407                                      local->flags,
408                                      &local->parent_info,
409                                      &my_error); 
410       if (info)
411         {
412           _g_local_file_info_get_nostat (info, filename, path, local->matcher);
413           g_file_info_set_file_type (info, file_type);
414           if (file_type == G_FILE_TYPE_SYMBOLIC_LINK)
415             g_file_info_set_is_symlink (info, TRUE);
416         }
417     }
418   g_free (path);
419
420   if (info == NULL)
421     {
422       /* Failed to get info */
423       /* If the file does not exist there might have been a race where
424        * the file was removed between the readdir and the stat, so we
425        * ignore the file. */
426       if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
427         {
428           g_error_free (my_error);
429           goto next_file;
430         }
431       else
432         g_propagate_error (error, my_error);
433     }
434
435   return info;
436 }
437
438 static gboolean
439 g_local_file_enumerator_close (GFileEnumerator  *enumerator,
440                                GCancellable     *cancellable,
441                                GError          **error)
442 {
443   GLocalFileEnumerator *local = G_LOCAL_FILE_ENUMERATOR (enumerator);
444
445   if (local->dir)
446     {
447 #ifdef USE_GDIR
448       g_dir_close (local->dir);
449 #else
450       closedir (local->dir);
451 #endif
452       local->dir = NULL;
453     }
454
455   return TRUE;
456 }