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