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