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 (mem: local->entries[i].name); |
99 | |
100 | g_free (mem: 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 (parent_info: &local->parent_info); |
114 | g_free (mem: local->filename); |
115 | g_file_attribute_matcher_unref (matcher: local->matcher); |
116 | g_file_attribute_matcher_unref (matcher: local->reduced_matcher); |
117 | if (local->dir) |
118 | { |
119 | #ifdef USE_GDIR |
120 | g_dir_close (local->dir); |
121 | #else |
122 | closedir (dirp: 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, subtract: tmp); |
196 | g_file_attribute_matcher_unref (matcher: 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 (name: filename); |
233 | if (dir == NULL) |
234 | { |
235 | gchar *utf8_filename; |
236 | errsv = errno; |
237 | |
238 | utf8_filename = g_filename_to_utf8 (opsysstring: filename, len: -1, NULL, NULL, NULL); |
239 | g_set_error (err: error, G_IO_ERROR, |
240 | code: g_io_error_from_errno (err_no: errsv), |
241 | format: "Error opening directory '%s': %s" , |
242 | utf8_filename, g_strerror (errnum: errsv)); |
243 | g_free (mem: utf8_filename); |
244 | g_free (mem: filename); |
245 | return NULL; |
246 | } |
247 | |
248 | #endif |
249 | |
250 | local = g_object_new (G_TYPE_LOCAL_FILE_ENUMERATOR, |
251 | first_property_name: "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 (matcher: 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 (mem: local->entries[i].name); |
322 | } |
323 | |
324 | for (i = 0; i < CHUNK_SIZE; i++) |
325 | { |
326 | entry = readdir (dirp: local->dir); |
327 | while (entry |
328 | && (0 == strcmp (s1: entry->d_name, s2: "." ) || |
329 | 0 == strcmp (s1: entry->d_name, s2: ".." ))) |
330 | entry = readdir (dirp: local->dir); |
331 | |
332 | if (entry) |
333 | { |
334 | local->entries[i].name = g_strdup (str: 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 (d_type: 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 (base: local->entries, nmemb: i, size: sizeof (DirEntry), compar: 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 (dir: local->filename, attribute_matcher: local->matcher, parent_info: &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: &file_type); |
389 | #endif |
390 | |
391 | if (filename == NULL) |
392 | return NULL; |
393 | |
394 | my_error = NULL; |
395 | path = g_build_filename (first_element: 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 (basename: filename, path, |
400 | attribute_matcher: local->matcher, |
401 | flags: local->flags, |
402 | parent_info: &local->parent_info, |
403 | error: &my_error); |
404 | } |
405 | else |
406 | { |
407 | info = _g_local_file_info_get (basename: filename, path, |
408 | attribute_matcher: local->reduced_matcher, |
409 | flags: local->flags, |
410 | parent_info: &local->parent_info, |
411 | error: &my_error); |
412 | if (info) |
413 | { |
414 | _g_local_file_info_get_nostat (info, basename: filename, path, attribute_matcher: local->matcher); |
415 | g_file_info_set_file_type (info, type: 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 (mem: 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 (error: my_error, G_IO_ERROR, code: G_IO_ERROR_NOT_FOUND)) |
429 | { |
430 | g_error_free (error: my_error); |
431 | goto next_file; |
432 | } |
433 | else |
434 | g_propagate_error (dest: error, src: 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 (dirp: local->dir); |
453 | #endif |
454 | local->dir = NULL; |
455 | } |
456 | |
457 | return TRUE; |
458 | } |
459 | |