Coverage Report

Created: 2025-06-24 07:01

/src/ghostpdl/base/gp_unifs.c
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (C) 2001-2023 Artifex Software, Inc.
2
   All Rights Reserved.
3
4
   This software is provided AS-IS with no warranty, either express or
5
   implied.
6
7
   This software is distributed under license and may not be copied,
8
   modified or distributed except as expressly authorized under the terms
9
   of the license contained in the file LICENSE in this distribution.
10
11
   Refer to licensing information at http://www.artifex.com or contact
12
   Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
13
   CA 94129, USA, for further information.
14
*/
15
16
17
/* "Unix-like" file system platform routines for Ghostscript */
18
19
/* prevent gp.h from defining fopen */
20
#define fopen fopen
21
22
#include "stdio_.h"             /* for FILENAME_MAX */
23
#include "memory_.h"
24
#include "string_.h"
25
#include "gx.h"
26
27
#include "gp.h"
28
#include "gpmisc.h"
29
#include "gsstruct.h"
30
#include "gsutil.h"             /* for string_match */
31
#include "stat_.h"
32
#include "dirent_.h"
33
#include "unistd_.h"
34
#include <stdlib.h>             /* for mkstemp/mktemp */
35
36
#if !defined(HAVE_FSEEKO)
37
#define ftello ftell
38
#define fseeko fseek
39
#define ftello64 ftell
40
#define fseeko64 fseek
41
#endif
42
43
/* Provide a definition of the maximum path length in case the system
44
 * headers don't define it. This should be gp_file_name_sizeof from
45
 * gp.h once that value is properly sent in a system-dependent way.
46
 * HP-UX 11i 11.11 incorrectly defines FILENAME_MAX as 14.
47
 */
48
#ifdef FILENAME_MAX
49
#  if FILENAME_MAX < 80  /* arbitrary */
50
#    undef FILENAME_MAX
51
#  endif
52
#endif
53
#ifndef FILENAME_MAX
54
#  define FILENAME_MAX 1024
55
#endif
56
57
/* Library routines not declared in a standard header */
58
extern char *mktemp(char *);
59
60
/* ------ File naming and accessing ------ */
61
62
/* Define the default scratch file name prefix. */
63
const char gp_scratch_file_name_prefix[] = "gs_";
64
65
/* Define the name of the null output file. */
66
const char gp_null_file_name[] = "/dev/null";
67
68
/* Define the name that designates the current directory. */
69
const char gp_current_directory_name[] = ".";
70
71
/* Create and open a scratch file with a given name prefix. */
72
/* Write the actual file name at fname. */
73
FILE *
74
gp_open_scratch_file_impl(const gs_memory_t *mem,
75
                          const char        *prefix,
76
                                char         fname[gp_file_name_sizeof],
77
                          const char        *mode,
78
                                int          remove)
79
287k
{       /* The -8 is for XXXXXX plus a possible final / and -. */
80
#ifdef GS_NO_FILESYSTEM
81
    return NULL;
82
#else
83
287k
    int prefix_length = strlen(prefix);
84
287k
    int len = gp_file_name_sizeof - prefix_length - 8;
85
287k
    FILE *fp;
86
87
287k
    if (gp_file_name_is_absolute(prefix, prefix_length))
88
0
        *fname = 0;
89
287k
    else if (gp_gettmpdir(fname, &len) != 0)
90
287k
        strcpy(fname, "/tmp/");
91
0
    else {
92
0
        if (strlen(fname) != 0 && fname[strlen(fname) - 1] != '/')
93
0
            strcat(fname, "/");
94
0
    }
95
287k
    if (strlen(fname) + prefix_length + 8 >= gp_file_name_sizeof)
96
0
        return 0;               /* file name too long */
97
287k
    strcat(fname, prefix);
98
    /* Prevent trailing X's in path from being converted by mktemp. */
99
287k
    if (*fname != 0 && fname[strlen(fname) - 1] == 'X')
100
0
        strcat(fname, "-");
101
287k
    strcat(fname, "XXXXXX");
102
103
287k
#ifdef HAVE_MKSTEMP
104
287k
    {
105
287k
        int file;
106
287k
        char ofname[gp_file_name_sizeof];
107
108
        /* save the old filename template in case mkstemp fails */
109
287k
        memcpy(ofname, fname, gp_file_name_sizeof);
110
287k
#  ifdef HAVE_MKSTEMP64
111
287k
        file = mkstemp64(fname);
112
#  else
113
        file = mkstemp(fname);
114
#  endif
115
287k
        if (file < 0) {
116
0
            emprintf1(mem, "**** Could not open temporary file %s\n", ofname);
117
0
            return NULL;
118
0
        }
119
#  if defined(O_LARGEFILE) && defined(__hpux)
120
        fcntl(file, F_SETFD, fcntl(file, F_GETFD) | O_LARGEFILE);
121
#  else
122
        /* Fixme : what to do with b64 and 32-bit mkstemp? Unimplemented. */
123
287k
#  endif
124
125
287k
        fp = fdopen(file, mode);
126
287k
        if (fp == NULL) {
127
0
      close(file);
128
0
  }
129
287k
    }
130
#else
131
    /* Coverity thinks that any use of mktemp() is insecure. But if we
132
    reach here then there is no mkstemp() alternative available, so there's
133
    not much we can do. Haven't been able to disable this - e.g. '//
134
    coverity[SECURE_TEMP]' doesn't have any affect. */
135
    mktemp(fname);
136
    fp = gp_fopentemp(fname, mode);
137
#endif
138
287k
    if (fp == NULL)
139
0
        emprintf1(mem, "**** Could not open temporary file %s\n", fname);
140
141
287k
    if (remove)
142
72.8k
        unlink(fname); /* unlink, not gp_unlink here. */
143
144
287k
    return fp;
145
287k
#endif
146
287k
}
147
148
/* Open a file with the given name, as a stream of uninterpreted bytes. */
149
FILE *
150
gp_fopen_impl(gs_memory_t *mem, const char *fname, const char *mode)
151
74.4M
{
152
74.4M
#if defined(HAVE_FILE64)
153
74.4M
    return fopen64(fname, mode);
154
#else
155
    return fopen(fname, mode);
156
#endif
157
74.4M
}
158
159
int
160
gp_unlink_impl(gs_memory_t *mem, const char *fname)
161
438k
{
162
438k
    return unlink(fname);
163
438k
}
164
165
int
166
gp_rename_impl(gs_memory_t *mem, const char *from, const char *to)
167
0
{
168
0
    return rename(from, to);
169
0
}
170
171
int gp_stat_impl(const gs_memory_t *mem, const char *path, struct stat *buf)
172
0
{
173
0
    return stat(path, buf);
174
0
}
175
176
int gp_can_share_fdesc(void)
177
1.80G
{
178
1.80G
#if defined(HAVE_PREAD_PWRITE) && HAVE_PREAD_PWRITE == 1
179
1.80G
    return 1;
180
#else
181
    return 0; /* can't share FILE * descriptors w/o pread due to seek..read..seek */
182
#endif
183
1.80G
}
184
185
FILE *gp_fdup_impl(FILE *f, const char *mode)
186
0
{
187
#ifdef GS_NO_FILESYSTEM
188
    return NULL;
189
#else
190
0
    int fd = fileno(f);
191
0
    if (fd < 0)
192
0
        return NULL;
193
0
    fd = dup(fd);
194
0
    if (fd < 0)
195
0
        return NULL;
196
0
    return fdopen(fd, mode);
197
0
#endif
198
0
}
199
200
int gp_pread_impl(char *buf, size_t count, gs_offset_t offset, FILE *f)
201
7.19M
{
202
#ifdef GS_NO_FILESYSTEM
203
    return 0;
204
#elif defined(HAVE_PREAD_PWRITE) && HAVE_PREAD_PWRITE == 1
205
    return pread(fileno(f), buf, count, offset);
206
#else
207
    int c;
208
    int64_t os, curroff = gp_ftell_impl(f);
209
    if (curroff < 0) return curroff;
210
211
    os = gp_fseek_impl(f, offset, 0);
212
    if (os < 0) return os;
213
214
    c = fread(buf, 1, count, f);
215
    if (c < 0) return c;
216
217
    os = gp_fseek_impl(f, curroff, 0);
218
    if (os < 0) return os;
219
220
    return c;
221
#endif
222
7.19M
}
223
224
int gp_pwrite_impl(const char *buf, size_t count, gs_offset_t offset, FILE *f)
225
42.3M
{
226
#ifdef GS_NO_FILESYSTEM
227
    return 0;
228
#elif defined(HAVE_PREAD_PWRITE) && HAVE_PREAD_PWRITE == 1
229
    return pwrite(fileno(f), buf, count, offset);
230
#else
231
    int c;
232
    int64_t os, curroff = gp_ftell_impl(f);
233
    if (curroff < 0) return curroff;
234
235
    os = gp_fseek_impl(f, offset, 0);
236
    if (os < 0) return os;
237
238
    c = fwrite(buf, 1, count, f);
239
    if (c < 0) return c;
240
241
    os = gp_fseek_impl(f, curroff, 0);
242
    if (os < 0) return os;
243
244
    return c;
245
#endif
246
42.3M
}
247
248
/* Set a file into binary or text mode. */
249
int
250
gp_setmode_binary_impl(FILE * pfile, bool mode) /* lgtm [cpp/useless-expression] */
251
72.2k
{
252
72.2k
    return 0;                   /* Noop under Unix */
253
72.2k
}
254
255
/* ------ File enumeration ------ */
256
257
/* Thanks to Fritz Elfert (Fritz_Elfert@wue.maus.de) for */
258
/* the original version of the following code, and Richard Mlynarik */
259
/* (mly@adoc.xerox.com) for an improved version. */
260
261
#ifdef GS_NO_FILESYSTEM
262
struct file_enum_s {
263
    int dummy;
264
};
265
266
static file_enum dummy_enum;
267
#else
268
typedef struct dirstack_s dirstack;
269
struct dirstack_s {
270
    dirstack *next;
271
    DIR *entry;
272
};
273
274
gs_private_st_ptrs1(st_dirstack, dirstack, "dirstack",
275
                    dirstack_enum_ptrs, dirstack_reloc_ptrs, next);
276
277
struct file_enum_s {
278
    DIR *dirp;                  /* pointer to current open directory   */
279
    char *pattern;              /* original pattern                    */
280
    char *work;                 /* current path                        */
281
    int worklen;                /* strlen (work)                       */
282
    dirstack *dstack;           /* directory stack                     */
283
    int patlen;
284
    int pathead;                /* how much of pattern to consider
285
                                 *  when listing files in current directory */
286
    bool first_time;
287
    gs_memory_t *memory;
288
};
289
gs_private_st_ptrs3(st_file_enum, struct file_enum_s, "file_enum",
290
          file_enum_enum_ptrs, file_enum_reloc_ptrs, pattern, work, dstack);
291
292
/* Private procedures */
293
294
/* Do a wild-card match. */
295
#ifdef DEBUG
296
static bool
297
wmatch(const byte * str, uint len, const byte * pstr, uint plen,
298
       const gs_memory_t *mem)
299
{
300
    bool match = string_match(str, len, pstr, plen, NULL);
301
302
    if (gs_debug_c('e')) {
303
        int i;
304
        dmlputs(mem, "[e]string_match(\"");
305
        for (i=0; i<len; i++)
306
            errprintf(mem, "%c", str[i]);
307
        dmputs(mem, "\", \"");
308
        for (i=0; i<plen; i++)
309
            errprintf(mem, "%c", pstr[i]);
310
        dmprintf1(mem, "\") = %s\n", (match ? "TRUE" : "false"));
311
    }
312
    return match;
313
}
314
#else
315
0
#define wmatch(S,L,PS,PL,M) string_match(S,L,PS,PL,NULL)
316
#endif
317
318
/* Search a string backward for a character. */
319
/* (This substitutes for strrchr, which some systems don't provide.) */
320
static char *
321
rchr(char *str, char ch, int len)
322
1.78M
{
323
1.78M
    register char *p = str + len;
324
325
3.57M
    while (p > str)
326
3.57M
        if (*--p == ch)
327
1.78M
            return p;
328
0
    return 0;
329
1.78M
}
330
331
/* Pop a directory from the enumeration stack. */
332
static bool
333
popdir(file_enum * pfen)
334
1.78M
{
335
1.78M
    dirstack *d = pfen->dstack;
336
337
1.78M
    if (d == 0)
338
1.78M
        return false;
339
0
    pfen->dirp = d->entry;
340
0
    pfen->dstack = d->next;
341
0
    gs_free_object(pfen->memory, d, "gp_enumerate_files(popdir)");
342
0
    return true;
343
1.78M
}
344
#endif
345
346
/* Initialize an enumeration. */
347
file_enum *
348
gp_enumerate_files_init_impl(gs_memory_t * mem, const char *pat, uint patlen)
349
1.78M
{
350
#ifdef GS_NO_FILESYSTEM
351
    return &dummy_enum;
352
#else
353
1.78M
    file_enum *pfen;
354
1.78M
    char *p;
355
1.78M
    char *work;
356
357
    /* Reject attempts to enumerate paths longer than the */
358
    /* system-dependent limit. */
359
1.78M
    if (patlen > FILENAME_MAX)
360
0
        return 0;
361
362
    /* Reject attempts to enumerate with a pattern containing zeroes. */
363
1.78M
    {
364
1.78M
        const char *p1;
365
366
87.2M
        for (p1 = pat; p1 < pat + patlen; p1++)
367
85.4M
            if (*p1 == 0)
368
0
                return 0;
369
1.78M
    }
370
    /* >>> Should crunch strings of repeated "/"'s in pat to a single "/"
371
     * >>>  to match stupid unix filesystem "conventions" */
372
373
1.78M
    pfen = gs_alloc_struct(mem, file_enum, &st_file_enum,
374
1.78M
                           "gp_enumerate_files");
375
1.78M
    if (pfen == 0)
376
0
        return 0;
377
378
    /* pattern and work could be allocated as strings, */
379
    /* but it's simpler for GC and freeing to allocate them as bytes. */
380
381
1.78M
    pfen->memory = mem;
382
1.78M
    pfen->dstack = 0;
383
1.78M
    pfen->first_time = true;
384
1.78M
    pfen->patlen = patlen;
385
1.78M
    pfen->work = 0;
386
1.78M
    pfen->pattern =
387
1.78M
        (char *)gs_alloc_bytes(mem, patlen + 1,
388
1.78M
                               "gp_enumerate_files(pattern)");
389
1.78M
    if (pfen->pattern == 0)
390
0
        goto fail1;
391
1.78M
    memcpy(pfen->pattern, pat, patlen);
392
1.78M
    pfen->pattern[patlen] = 0;
393
394
1.78M
    work = (char *)gs_alloc_bytes(mem, FILENAME_MAX + 1,
395
1.78M
                                  "gp_enumerate_files(work)");
396
1.78M
    if (work == 0)
397
0
        goto fail2;
398
1.78M
    pfen->work = work;
399
400
1.78M
    p = work;
401
1.78M
    memcpy(p, pat, patlen);
402
1.78M
    p += patlen;
403
1.78M
    *p = 0;
404
405
    /* Remove directory specifications beyond the first wild card. */
406
    /* Some systems don't have strpbrk, so we code it open. */
407
1.78M
    p = pfen->work;
408
85.4M
    while (*p != '*' && *p != '?' && *p != 0)
409
83.6M
        p++;
410
3.57M
    while (*p != '/' && *p != 0)
411
1.78M
        p++;
412
1.78M
    if (*p == '/')
413
0
        *p = 0;
414
    /* Substring for first wildcard match */
415
1.78M
    pfen->pathead = p - work;
416
417
    /* Select the next higher directory-level. */
418
1.78M
    p = rchr(work, '/', p - work);
419
1.78M
    if (!p) {                   /* No directory specification */
420
0
        work[0] = 0;
421
0
        pfen->worklen = 0;
422
1.78M
    } else {
423
1.78M
        if (p == work) {        /* Root directory -- don't turn "/" into "" */
424
0
            p++;
425
0
        }
426
1.78M
        *p = 0;
427
1.78M
        pfen->worklen = p - work;
428
1.78M
    }
429
430
1.78M
    return pfen;
431
432
0
fail2:
433
0
    gs_free_object(mem, pfen->pattern, "gp_enumerate_files(pattern)");
434
0
fail1:
435
0
    gs_free_object(mem, pfen, "gp_enumerate_files");
436
0
    return NULL;
437
0
#endif
438
0
}
439
440
/* Enumerate the next file. */
441
uint
442
gp_enumerate_files_next_impl(gs_memory_t * mem, file_enum * pfen, char *ptr, uint maxlen)
443
1.78M
{
444
#ifdef GS_NO_FILESYSTEM
445
    return ~(uint)0;
446
#else
447
1.78M
    const dir_entry *de;
448
1.78M
    char *work = pfen->work;
449
1.78M
    int worklen = pfen->worklen;
450
1.78M
    char *pattern = pfen->pattern;
451
1.78M
    int pathead = pfen->pathead;
452
1.78M
    int len;
453
454
1.78M
    if (pfen->first_time) {
455
1.78M
        pfen->dirp = ((worklen == 0) ? opendir(".") : opendir(work));
456
1.78M
        if_debug1m('e', pfen->memory, "[e]file_enum:First-Open '%s'\n", work);
457
1.78M
        pfen->first_time = false;
458
1.78M
        if (pfen->dirp == 0) {  /* first opendir failed */
459
1.78M
            gp_enumerate_files_close(mem, pfen);
460
1.78M
            return ~(uint) 0;
461
1.78M
        }
462
1.78M
    }
463
0
  top:de = readdir(pfen->dirp);
464
0
    if (de == 0) {              /* No more entries in this directory */
465
0
        char *p;
466
467
0
        if_debug0m('e', pfen->memory, "[e]file_enum:Closedir\n");
468
0
        closedir(pfen->dirp);
469
        /* Back working directory and matching pattern up one level */
470
0
        p = rchr(work, '/', worklen);
471
0
        if (p != 0) {
472
0
            if (p == work)
473
0
                p++;
474
0
            *p = 0;
475
0
            worklen = p - work;
476
0
        } else
477
0
            worklen = 0;
478
0
        if (pathead != pfen->patlen) {
479
0
            p = rchr(pattern, '/', pathead);
480
0
            if (p != 0)
481
0
                pathead = p - pattern;
482
0
            else
483
0
                pathead = 0;
484
0
        }
485
486
0
        if (popdir(pfen)) {     /* Back up the directory tree. */
487
0
            if_debug1m('e', pfen->memory, "[e]file_enum:Dir popped '%s'\n", work);
488
0
            goto top;
489
0
        } else {
490
0
            if_debug0m('e', pfen->memory, "[e]file_enum:Dirstack empty\n");
491
0
            gp_enumerate_files_close(mem, pfen);
492
0
            return ~(uint) 0;
493
0
        }
494
0
    }
495
    /* Skip . and .. */
496
0
    len = strlen(de->d_name);
497
0
    if (len <= 2 && (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")))
498
0
        goto top;
499
0
    if (len + worklen + 1 > FILENAME_MAX)
500
        /* Should be an error, I suppose */
501
0
        goto top;
502
0
    if (worklen == 0) {         /* "Current" directory (evil un*x kludge) */
503
0
        memcpy(work, de->d_name, len + 1);
504
0
    } else if (worklen == 1 && work[0] == '/') {        /* Root directory */
505
0
        memcpy(work + 1, de->d_name, len + 1);
506
0
        len = len + 1;
507
0
    } else {
508
0
        work[worklen] = '/';
509
0
        memcpy(work + worklen + 1, de->d_name, len + 1);
510
0
        len = worklen + 1 + len;
511
0
    }
512
513
    /* Test for a match at this directory level */
514
0
    if (!wmatch((byte *) work, len, (byte *) pattern, pathead, pfen->memory))
515
0
        goto top;
516
517
    /* Perhaps descend into subdirectories */
518
0
    if (pathead < maxlen) {
519
        /* Using stat() to decide whether this item is a directory then opening
520
        using opendir(), results in Coverity complaining about races. Se
521
        instead we simple call opendir() immediately and look at whether it
522
        succeeded. */
523
0
        DIR *dp = opendir(work);
524
0
        if (!dp) {
525
            /* Not a directory. */
526
0
            goto winner;
527
0
        }
528
529
0
        if (pfen->patlen == pathead + 1) {      /* Listing "foo/?/" -- return this entry */
530
0
            closedir(dp);
531
0
            work[len++] = '/';
532
0
            goto winner;
533
0
        }
534
535
        /* >>> Should optimise the case in which the next level */
536
        /* >>> of directory has no wildcards. */
537
#ifdef DEBUG
538
        {
539
            char save_end = pattern[pathead];
540
541
            pattern[pathead] = 0;
542
            if_debug2m('e', pfen->memory, "[e]file_enum:fname='%s', p='%s'\n",
543
                       work, pattern);
544
            pattern[pathead] = save_end;
545
        }
546
#endif /* DEBUG */
547
0
        {                  /* Advance to the next directory-delimiter */
548
            /* in pattern */
549
0
            char *p;
550
0
            dirstack *d;
551
552
0
      if (pattern[pathead] == 0) {
553
0
                pathead = pfen->patlen;
554
0
      } else {
555
0
                for (p = pattern + pathead + 1;; p++) {
556
0
                    if (*p == 0) {  /* No more subdirectories to match */
557
0
                        pathead = pfen->patlen;
558
0
                        break;
559
0
                    } else if (*p == '/') {
560
0
                        pathead = p - pattern;
561
0
                        break;
562
0
                    }
563
0
                }
564
0
            }
565
566
            /* Push a directory onto the enumeration stack. */
567
0
            d = gs_alloc_struct(pfen->memory, dirstack,
568
0
                                &st_dirstack,
569
0
                                "gp_enumerate_files(pushdir)");
570
0
            if (d != 0) {
571
0
                d->next = pfen->dstack;
572
0
                d->entry = pfen->dirp;
573
0
                pfen->dstack = d;
574
0
            } else
575
0
                DO_NOTHING;     /* >>> gs_error_VMerror!!! */
576
577
0
            if_debug1m('e', pfen->memory, "[e]file_enum:Dir pushed '%s'\n",
578
0
                       work);
579
0
            worklen = len;
580
0
            pfen->dirp = dp;
581
0
            goto top;
582
0
        }
583
0
    }
584
0
  winner:
585
    /* We have a winner! */
586
0
    pfen->worklen = worklen;
587
0
    pfen->pathead = pathead;
588
0
    memcpy(ptr, work, len > maxlen ? maxlen : len);
589
590
0
    return len;
591
0
#endif
592
0
}
593
594
/* Clean up the file enumeration. */
595
void
596
gp_enumerate_files_close_impl(gs_memory_t * mem, file_enum * pfen)
597
1.78M
{
598
#ifdef GS_NO_FILESYSTEM
599
    /* No cleanup necessary */
600
#else
601
1.78M
    gs_memory_t *mem2 = pfen->memory;
602
1.78M
    (void)mem;
603
604
1.78M
    if_debug0m('e', mem2, "[e]file_enum:Cleanup\n");
605
1.78M
    while (popdir(pfen))        /* clear directory stack */
606
1.78M
        DO_NOTHING;
607
1.78M
    gs_free_object(mem2, (byte *) pfen->work,
608
1.78M
                   "gp_enumerate_close(work)");
609
1.78M
    gs_free_object(mem2, (byte *) pfen->pattern,
610
1.78M
                   "gp_enumerate_files_close(pattern)");
611
1.78M
    gs_free_object(mem2, pfen, "gp_enumerate_files_close");
612
1.78M
#endif
613
1.78M
}
614
615
/* Test-cases:
616
   (../?*r*?/?*.ps) {==} 100 string filenameforall
617
   (../?*r*?/?*.ps*) {==} 100 string filenameforall
618
   (../?*r*?/) {==} 100 string filenameforall
619
   (/t*?/?*.ps) {==} 100 string filenameforall
620
 */
621
622
/* --------- 64 bit file access ----------- */
623
624
gs_offset_t gp_ftell_impl(FILE *strm)
625
4.98M
{
626
4.98M
#if defined(HAVE_FILE64)
627
4.98M
    return ftello64(strm);
628
#else
629
    return ftello(strm);
630
#endif
631
4.98M
}
632
633
int gp_fseek_impl(FILE *strm, gs_offset_t offset, int origin)
634
18.0M
{
635
18.0M
#if defined(HAVE_FILE64)
636
18.0M
    return fseeko64(strm, offset, origin);
637
#else
638
    off_t offset1 = (off_t)offset;
639
640
    if (offset != offset1)
641
        return -1;
642
    return fseeko(strm, offset1, origin);
643
#endif
644
18.0M
}
645
646
bool gp_fseekable_impl(FILE *f)
647
4.18k
{
648
4.18k
    struct stat s;
649
4.18k
    int fno;
650
651
4.18k
    fno = fileno(f);
652
4.18k
    if (fno < 0)
653
0
        return(false);
654
655
4.18k
    if (fstat(fno, &s) < 0)
656
0
        return(false);
657
658
4.18k
    return((bool)S_ISREG(s.st_mode));
659
4.18k
}