Coverage Report

Created: 2025-06-13 06:55

/src/glib/gio/glocalfileenumerator.c
Line
Count
Source (jump to first uncovered line)
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
0
#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
0
{
95
0
#ifndef USE_GDIR
96
0
  int i;
97
98
0
  if (local->entries != NULL)
99
0
    {
100
0
      for (i = 0; local->entries[i].name != NULL; i++)
101
0
  g_free (local->entries[i].name);
102
      
103
0
      g_free (local->entries);
104
0
    }
105
0
#endif
106
0
}
107
108
static void
109
g_local_file_enumerator_finalize (GObject *object)
110
0
{
111
0
  GLocalFileEnumerator *local;
112
113
0
  local = G_LOCAL_FILE_ENUMERATOR (object);
114
115
0
  if (local->got_parent_info)
116
0
    _g_local_file_info_free_parent_info (&local->parent_info);
117
0
  g_free (local->filename);
118
0
  g_file_attribute_matcher_unref (local->matcher);
119
0
  g_file_attribute_matcher_unref (local->reduced_matcher);
120
0
  if (local->dir)
121
0
    {
122
#ifdef USE_GDIR
123
      g_dir_close (local->dir);
124
#else
125
0
      closedir (local->dir);
126
0
#endif      
127
0
      local->dir = NULL;
128
0
    }
129
130
0
  free_entries (local);
131
132
0
  G_OBJECT_CLASS (g_local_file_enumerator_parent_class)->finalize (object);
133
0
}
134
135
136
static void
137
g_local_file_enumerator_class_init (GLocalFileEnumeratorClass *klass)
138
0
{
139
0
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
140
0
  GFileEnumeratorClass *enumerator_class = G_FILE_ENUMERATOR_CLASS (klass);
141
  
142
0
  gobject_class->finalize = g_local_file_enumerator_finalize;
143
144
0
  enumerator_class->next_file = g_local_file_enumerator_next_file;
145
0
  enumerator_class->close_fn = g_local_file_enumerator_close;
146
0
}
147
148
static void
149
g_local_file_enumerator_init (GLocalFileEnumerator *local)
150
0
{
151
0
}
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
0
{
195
0
  GFileAttributeMatcher *result, *tmp;
196
197
0
  tmp = g_file_attribute_matcher_new (attributes);
198
0
  result = g_file_attribute_matcher_subtract (matcher, tmp);
199
0
  g_file_attribute_matcher_unref (tmp);
200
201
0
  return result;
202
0
}
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
0
{
212
0
  GLocalFileEnumerator *local;
213
0
  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
0
  DIR *dir;
233
0
  int errsv;
234
235
0
  dir = opendir (filename);
236
0
  if (dir == NULL)
237
0
    {
238
0
      gchar *utf8_filename;
239
0
      errsv = errno;
240
241
0
      utf8_filename = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
242
0
      g_set_error (error, G_IO_ERROR,
243
0
                   g_io_error_from_errno (errsv),
244
0
                   "Error opening directory '%s': %s",
245
0
                   utf8_filename, g_strerror (errsv));
246
0
      g_free (utf8_filename);
247
0
      g_free (filename);
248
0
      return NULL;
249
0
    }
250
251
0
#endif
252
  
253
0
  local = g_object_new (G_TYPE_LOCAL_FILE_ENUMERATOR,
254
0
                        "container", file,
255
0
                        NULL);
256
257
0
  local->dir = dir;
258
0
  local->filename = filename;
259
0
  local->matcher = g_file_attribute_matcher_new (attributes);
260
0
#ifndef USE_GDIR
261
0
  local->reduced_matcher = g_file_attribute_matcher_subtract_attributes (local->matcher,
262
0
                                                                         G_LOCAL_FILE_INFO_NOSTAT_ATTRIBUTES","
263
0
                                                                         "standard::type");
264
0
#endif
265
0
  local->flags = flags;
266
  
267
0
  return G_FILE_ENUMERATOR (local);
268
0
}
269
270
#ifndef USE_GDIR
271
static int
272
sort_by_inode (const void *_a, const void *_b)
273
0
{
274
0
  const DirEntry *a, *b;
275
276
0
  a = _a;
277
0
  b = _b;
278
0
  return a->inode - b->inode;
279
0
}
280
281
#ifdef HAVE_STRUCT_DIRENT_D_TYPE
282
static GFileType
283
file_type_from_dirent (char d_type)
284
0
{
285
0
  switch (d_type)
286
0
    {
287
0
    case DT_BLK:
288
0
    case DT_CHR:
289
0
    case DT_FIFO:
290
0
    case DT_SOCK:
291
0
      return G_FILE_TYPE_SPECIAL;
292
0
    case DT_DIR:
293
0
      return G_FILE_TYPE_DIRECTORY;
294
0
    case DT_LNK:
295
0
      return G_FILE_TYPE_SYMBOLIC_LINK;
296
0
    case DT_REG:
297
0
      return G_FILE_TYPE_REGULAR;
298
0
    case DT_UNKNOWN:
299
0
    default:
300
0
      return G_FILE_TYPE_UNKNOWN;
301
0
    }
302
0
}
303
#endif
304
305
static const char *
306
next_file_helper (GLocalFileEnumerator *local, GFileType *file_type)
307
0
{
308
0
  struct dirent *entry;
309
0
  const char *filename;
310
0
  int i;
311
312
0
  if (local->at_end)
313
0
    return NULL;
314
  
315
0
  if (local->entries == NULL ||
316
0
      (local->entries[local->entries_pos].name == NULL))
317
0
    {
318
0
      if (local->entries == NULL)
319
0
  local->entries = g_new (DirEntry, CHUNK_SIZE + 1);
320
0
      else
321
0
  {
322
    /* Restart by clearing old names */
323
0
    for (i = 0; local->entries[i].name != NULL; i++)
324
0
      g_free (local->entries[i].name);
325
0
  }
326
      
327
0
      for (i = 0; i < CHUNK_SIZE; i++)
328
0
  {
329
0
    entry = readdir (local->dir);
330
0
    while (entry 
331
0
     && (0 == strcmp (entry->d_name, ".") ||
332
0
         0 == strcmp (entry->d_name, "..")))
333
0
      entry = readdir (local->dir);
334
335
0
    if (entry)
336
0
      {
337
0
        local->entries[i].name = g_strdup (entry->d_name);
338
0
        local->entries[i].inode = entry->d_ino;
339
0
#if HAVE_STRUCT_DIRENT_D_TYPE
340
0
              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
0
      }
345
0
    else
346
0
      break;
347
0
  }
348
0
      local->entries[i].name = NULL;
349
0
      local->entries_pos = 0;
350
      
351
0
      qsort (local->entries, i, sizeof (DirEntry), sort_by_inode);
352
0
    }
353
354
0
  filename = local->entries[local->entries_pos].name;
355
0
  if (filename == NULL)
356
0
    local->at_end = TRUE;
357
    
358
0
  *file_type = local->entries[local->entries_pos].type;
359
360
0
  local->entries_pos++;
361
362
0
  return filename;
363
0
}
364
365
#endif
366
367
static GFileInfo *
368
g_local_file_enumerator_next_file (GFileEnumerator  *enumerator,
369
           GCancellable     *cancellable,
370
           GError          **error)
371
0
{
372
0
  GLocalFileEnumerator *local = G_LOCAL_FILE_ENUMERATOR (enumerator);
373
0
  const char *filename;
374
0
  char *path;
375
0
  GFileInfo *info;
376
0
  GError *my_error;
377
0
  GFileType file_type;
378
379
0
  if (!local->got_parent_info)
380
0
    {
381
0
      _g_local_file_info_get_parent_info (local->filename, local->matcher, &local->parent_info);
382
0
      local->got_parent_info = TRUE;
383
0
    }
384
385
0
 next_file:
386
387
0
  if (g_cancellable_set_error_if_cancelled (cancellable, error))
388
0
    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
0
  filename = next_file_helper (local, &file_type);
395
0
#endif
396
397
0
  if (filename == NULL)
398
0
    return NULL;
399
400
0
  my_error = NULL;
401
0
  path = g_build_filename (local->filename, filename, NULL);
402
0
  if (file_type == G_FILE_TYPE_UNKNOWN ||
403
0
      (file_type == G_FILE_TYPE_SYMBOLIC_LINK && !(local->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)))
404
0
    {
405
0
      info = _g_local_file_info_get (filename, path,
406
0
                                     local->matcher,
407
0
                                     local->flags,
408
0
                                     &local->parent_info,
409
0
                                     &my_error); 
410
0
    }
411
0
  else
412
0
    {
413
0
      info = _g_local_file_info_get (filename, path,
414
0
                                     local->reduced_matcher,
415
0
                                     local->flags,
416
0
                                     &local->parent_info,
417
0
                                     &my_error); 
418
0
      if (info)
419
0
        {
420
0
          _g_local_file_info_get_nostat (info, filename, path, local->matcher);
421
0
          g_file_info_set_file_type (info, file_type);
422
0
          if (file_type == G_FILE_TYPE_SYMBOLIC_LINK)
423
0
            g_file_info_set_is_symlink (info, TRUE);
424
0
        }
425
0
    }
426
0
  g_free (path);
427
428
0
  if (info == NULL)
429
0
    {
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
0
      if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
435
0
  {
436
0
    g_error_free (my_error);
437
0
    goto next_file;
438
0
  }
439
0
      else
440
0
  g_propagate_error (error, my_error);
441
0
    }
442
443
0
  return info;
444
0
}
445
446
static gboolean
447
g_local_file_enumerator_close (GFileEnumerator  *enumerator,
448
             GCancellable     *cancellable,
449
             GError          **error)
450
0
{
451
0
  GLocalFileEnumerator *local = G_LOCAL_FILE_ENUMERATOR (enumerator);
452
453
0
  if (local->dir)
454
0
    {
455
#ifdef USE_GDIR
456
      g_dir_close (local->dir);
457
#else
458
0
      closedir (local->dir);
459
0
#endif
460
0
      local->dir = NULL;
461
0
    }
462
463
0
  return TRUE;
464
0
}