Coverage Report

Created: 2026-05-16 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wget2/lib/fcntl.c
Line
Count
Source
1
/* Provide file descriptor control.
2
3
   Copyright (C) 2009-2026 Free Software Foundation, Inc.
4
5
   This file is free software: you can redistribute it and/or modify
6
   it under the terms of the GNU Lesser General Public License as
7
   published by the Free Software Foundation; either version 2.1 of the
8
   License, or (at your option) any later version.
9
10
   This file 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
13
   GNU Lesser General Public License for more details.
14
15
   You should have received a copy of the GNU Lesser General Public License
16
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17
18
/* Written by Eric Blake <ebb9@byu.net>.  */
19
20
#include <config.h>
21
22
/* Specification.  */
23
#include <fcntl.h>
24
25
#include <errno.h>
26
#include <limits.h>
27
#include <stdarg.h>
28
#include <stdlib.h>
29
#include <unistd.h>
30
31
#ifdef __KLIBC__
32
# include <emx/io.h>
33
# include <InnoTekLIBC/backend.h>
34
#endif
35
36
#if defined _WIN32 && ! defined __CYGWIN__
37
/* Get declarations of the native Windows API functions.  */
38
# define WIN32_LEAN_AND_MEAN
39
# include <windows.h>
40
41
/* Get _get_osfhandle.  */
42
# if GNULIB_MSVC_NOTHROW
43
#  include "msvc-nothrow.h"
44
# else
45
#  include <io.h>
46
# endif
47
48
/* Upper bound on getdtablesize().  See lib/getdtablesize.c.  */
49
# define OPEN_MAX_MAX 0x10000
50
51
/* Duplicate OLDFD into the first available slot of at least NEWFD,
52
   which must be positive, with FLAGS determining whether the duplicate
53
   will be inheritable.  */
54
static int
55
dupfd (int oldfd, int newfd, int flags)
56
{
57
  /* Mingw has no way to create an arbitrary fd.  Iterate until all
58
     file descriptors less than newfd are filled up.  */
59
60
  if (newfd < 0 || getdtablesize () <= newfd)
61
    {
62
      errno = EINVAL;
63
      return -1;
64
    }
65
66
  HANDLE old_handle = (HANDLE) _get_osfhandle (oldfd);
67
  int mode;
68
  if (old_handle == INVALID_HANDLE_VALUE
69
      || (mode = _setmode (oldfd, O_BINARY)) == -1)
70
    {
71
      /* oldfd is not open, or is an unassigned standard file
72
         descriptor.  */
73
      errno = EBADF;
74
      return -1;
75
    }
76
  _setmode (oldfd, mode);
77
  flags |= mode;
78
79
  HANDLE curr_process = GetCurrentProcess ();
80
  BOOL inherit = flags & O_CLOEXEC ? FALSE : TRUE;
81
  unsigned char fds_to_close[OPEN_MAX_MAX / CHAR_BIT];
82
  unsigned int fds_to_close_bound = 0;
83
  int result;
84
  for (;;)
85
    {
86
      HANDLE new_handle;
87
      int duplicated_fd;
88
      unsigned int index;
89
90
      if (!DuplicateHandle (curr_process,           /* SourceProcessHandle */
91
                            old_handle,             /* SourceHandle */
92
                            curr_process,           /* TargetProcessHandle */
93
                            (PHANDLE) &new_handle,  /* TargetHandle */
94
                            (DWORD) 0,              /* DesiredAccess */
95
                            inherit,                /* InheritHandle */
96
                            DUPLICATE_SAME_ACCESS)) /* Options */
97
        {
98
          switch (GetLastError ())
99
            {
100
              case ERROR_TOO_MANY_OPEN_FILES:
101
                errno = EMFILE;
102
                break;
103
              case ERROR_INVALID_HANDLE:
104
              case ERROR_INVALID_TARGET_HANDLE:
105
              case ERROR_DIRECT_ACCESS_HANDLE:
106
                errno = EBADF;
107
                break;
108
              case ERROR_INVALID_PARAMETER:
109
              case ERROR_INVALID_FUNCTION:
110
              case ERROR_INVALID_ACCESS:
111
                errno = EINVAL;
112
                break;
113
              default:
114
                errno = EACCES;
115
                break;
116
            }
117
          result = -1;
118
          break;
119
        }
120
      duplicated_fd = _open_osfhandle ((intptr_t) new_handle, flags);
121
      if (duplicated_fd < 0)
122
        {
123
          CloseHandle (new_handle);
124
          result = -1;
125
          break;
126
        }
127
      if (newfd <= duplicated_fd)
128
        {
129
          result = duplicated_fd;
130
          break;
131
        }
132
133
      /* Set the bit duplicated_fd in fds_to_close[].  */
134
      index = (unsigned int) duplicated_fd / CHAR_BIT;
135
      if (fds_to_close_bound <= index)
136
        {
137
          if (sizeof fds_to_close <= index)
138
            /* Need to increase OPEN_MAX_MAX.  */
139
            abort ();
140
          memset (fds_to_close + fds_to_close_bound, '\0',
141
                  index + 1 - fds_to_close_bound);
142
          fds_to_close_bound = index + 1;
143
        }
144
      fds_to_close[index] |= 1 << ((unsigned int) duplicated_fd % CHAR_BIT);
145
    }
146
147
  /* Close the previous fds that turned out to be too small.  */
148
  {
149
    int saved_errno = errno;
150
151
    for (unsigned int duplicated_fd = 0;
152
         duplicated_fd < fds_to_close_bound * CHAR_BIT;
153
         duplicated_fd++)
154
      if ((fds_to_close[duplicated_fd / CHAR_BIT]
155
           >> (duplicated_fd % CHAR_BIT))
156
          & 1)
157
        close (duplicated_fd);
158
159
    errno = saved_errno;
160
  }
161
162
# if REPLACE_FCHDIR
163
  if (0 <= result)
164
    result = _gl_register_dup (oldfd, result);
165
# endif
166
  return result;
167
}
168
#endif /* W32 */
169
170
/* Forward declarations, because we '#undef fcntl' in the middle of this
171
   compilation unit.  */
172
/* Our implementation of fcntl (fd, F_DUPFD, target).  */
173
static int rpl_fcntl_DUPFD (int fd, int target);
174
/* Our implementation of fcntl (fd, F_DUPFD_CLOEXEC, target).  */
175
static int rpl_fcntl_DUPFD_CLOEXEC (int fd, int target);
176
#ifdef __KLIBC__
177
/* Adds support for fcntl on directories.  */
178
static int klibc_fcntl (int fd, int action, /* arg */...);
179
#endif
180
181
182
/* Perform the specified ACTION on the file descriptor FD, possibly
183
   using the argument ARG further described below.  This replacement
184
   handles the following actions, and forwards all others on to the
185
   native fcntl.  An unrecognized ACTION returns -1 with errno set to
186
   EINVAL.
187
188
   F_DUPFD - duplicate FD, with int ARG being the minimum target fd.
189
   If successful, return the duplicate, which will be inheritable;
190
   otherwise return -1 and set errno.
191
192
   F_DUPFD_CLOEXEC - duplicate FD, with int ARG being the minimum
193
   target fd.  If successful, return the duplicate, which will not be
194
   inheritable; otherwise return -1 and set errno.
195
196
   F_GETFD - ARG need not be present.  If successful, return a
197
   non-negative value containing the descriptor flags of FD (only
198
   FD_CLOEXEC is portable, but other flags may be present); otherwise
199
   return -1 and set errno.  */
200
201
int
202
fcntl (int fd, int action, /* arg */...)
203
#undef fcntl
204
#ifdef __KLIBC__
205
# define fcntl klibc_fcntl
206
#endif
207
7.77k
{
208
7.77k
  va_list arg;
209
7.77k
  va_start (arg, action);
210
211
7.77k
  int result = -1;
212
7.77k
  switch (action)
213
7.77k
    {
214
0
    case F_DUPFD:
215
0
      {
216
0
        int target = va_arg (arg, int);
217
0
        result = rpl_fcntl_DUPFD (fd, target);
218
0
        break;
219
0
      }
220
221
0
    case F_DUPFD_CLOEXEC:
222
0
      {
223
0
        int target = va_arg (arg, int);
224
0
        result = rpl_fcntl_DUPFD_CLOEXEC (fd, target);
225
0
        break;
226
0
      }
227
228
#if !HAVE_FCNTL
229
    case F_GETFD:
230
      {
231
# if defined _WIN32 && ! defined __CYGWIN__
232
        HANDLE handle = (HANDLE) _get_osfhandle (fd);
233
        DWORD flags;
234
        if (handle == INVALID_HANDLE_VALUE
235
            || GetHandleInformation (handle, &flags) == 0)
236
          errno = EBADF;
237
        else
238
          result = (flags & HANDLE_FLAG_INHERIT) ? 0 : FD_CLOEXEC;
239
# else /* !W32 */
240
        /* Use dup2 to reject invalid file descriptors.  No way to
241
           access this information, so punt.  */
242
        if (0 <= dup2 (fd, fd))
243
          result = 0;
244
# endif /* !W32 */
245
        break;
246
      } /* F_GETFD */
247
#endif /* !HAVE_FCNTL */
248
249
      /* Implementing F_SETFD on mingw is not trivial - there is no
250
         API for changing the O_NOINHERIT bit on an fd, and merely
251
         changing the HANDLE_FLAG_INHERIT bit on the underlying handle
252
         can lead to odd state.  It may be possible by duplicating the
253
         handle, using _open_osfhandle with the right flags, then
254
         using dup2 to move the duplicate onto the original, but that
255
         is not supported for now.  */
256
257
7.77k
    default:
258
7.77k
      {
259
7.77k
#if HAVE_FCNTL
260
7.77k
        switch (action)
261
7.77k
          {
262
          #ifdef F_BARRIERFSYNC                  /* macOS */
263
          case F_BARRIERFSYNC:
264
          #endif
265
          #ifdef F_CHKCLEAN                      /* macOS */
266
          case F_CHKCLEAN:
267
          #endif
268
          #ifdef F_CLOSEM                        /* NetBSD, HP-UX */
269
          case F_CLOSEM:
270
          #endif
271
          #ifdef F_FLUSH_DATA                    /* macOS */
272
          case F_FLUSH_DATA:
273
          #endif
274
          #ifdef F_FREEZE_FS                     /* macOS */
275
          case F_FREEZE_FS:
276
          #endif
277
          #ifdef F_FULLFSYNC                     /* macOS */
278
          case F_FULLFSYNC:
279
          #endif
280
          #ifdef F_GETCONFINED                   /* macOS */
281
          case F_GETCONFINED:
282
          #endif
283
          #ifdef F_GETDEFAULTPROTLEVEL           /* macOS */
284
          case F_GETDEFAULTPROTLEVEL:
285
          #endif
286
0
          #ifdef F_GETFD                         /* POSIX */
287
0
          case F_GETFD:
288
0
          #endif
289
0
          #ifdef F_GETFL                         /* POSIX */
290
3.88k
          case F_GETFL:
291
3.88k
          #endif
292
3.88k
          #ifdef F_GETLEASE                      /* Linux */
293
3.88k
          case F_GETLEASE:
294
3.88k
          #endif
295
          #ifdef F_GETNOSIGPIPE                  /* macOS */
296
          case F_GETNOSIGPIPE:
297
          #endif
298
3.88k
          #ifdef F_GETOWN                        /* POSIX */
299
3.88k
          case F_GETOWN:
300
3.88k
          #endif
301
3.88k
          #ifdef F_GETPIPE_SZ                    /* Linux */
302
3.88k
          case F_GETPIPE_SZ:
303
3.88k
          #endif
304
          #ifdef F_GETPROTECTIONCLASS            /* macOS */
305
          case F_GETPROTECTIONCLASS:
306
          #endif
307
          #ifdef F_GETPROTECTIONLEVEL            /* macOS */
308
          case F_GETPROTECTIONLEVEL:
309
          #endif
310
3.88k
          #ifdef F_GET_SEALS                     /* Linux */
311
3.88k
          case F_GET_SEALS:
312
3.88k
          #endif
313
3.88k
          #ifdef F_GETSIG                        /* Linux */
314
3.88k
          case F_GETSIG:
315
3.88k
          #endif
316
          #ifdef F_MAXFD                         /* NetBSD */
317
          case F_MAXFD:
318
          #endif
319
          #ifdef F_RECYCLE                       /* macOS */
320
          case F_RECYCLE:
321
          #endif
322
          #ifdef F_SETFIFOENH                    /* HP-UX */
323
          case F_SETFIFOENH:
324
          #endif
325
          #ifdef F_THAW_FS                       /* macOS */
326
          case F_THAW_FS:
327
          #endif
328
            /* These actions take no argument.  */
329
3.88k
            result = fcntl (fd, action);
330
3.88k
            break;
331
332
0
          #ifdef F_ADD_SEALS                     /* Linux */
333
0
          case F_ADD_SEALS:
334
0
          #endif
335
          #ifdef F_BADFD                         /* Solaris */
336
          case F_BADFD:
337
          #endif
338
          #ifdef F_CHECK_OPENEVT                 /* macOS */
339
          case F_CHECK_OPENEVT:
340
          #endif
341
          #ifdef F_DUP2FD                        /* FreeBSD, AIX, Solaris */
342
          case F_DUP2FD:
343
          #endif
344
          #ifdef F_DUP2FD_CLOEXEC                /* FreeBSD, Solaris */
345
          case F_DUP2FD_CLOEXEC:
346
          #endif
347
          #ifdef F_DUP2FD_CLOFORK                /* Solaris */
348
          case F_DUP2FD_CLOFORK:
349
          #endif
350
0
          #ifdef F_DUPFD                         /* POSIX */
351
0
          case F_DUPFD:
352
0
          #endif
353
0
          #ifdef F_DUPFD_CLOEXEC                 /* POSIX */
354
0
          case F_DUPFD_CLOEXEC:
355
0
          #endif
356
          #ifdef F_DUPFD_CLOFORK                 /* Solaris */
357
          case F_DUPFD_CLOFORK:
358
          #endif
359
          #ifdef F_GETXFL                        /* Solaris */
360
          case F_GETXFL:
361
          #endif
362
          #ifdef F_GLOBAL_NOCACHE                /* macOS */
363
          case F_GLOBAL_NOCACHE:
364
          #endif
365
          #ifdef F_MAKECOMPRESSED                /* macOS */
366
          case F_MAKECOMPRESSED:
367
          #endif
368
          #ifdef F_MOVEDATAEXTENTS               /* macOS */
369
          case F_MOVEDATAEXTENTS:
370
          #endif
371
          #ifdef F_NOCACHE                       /* macOS */
372
          case F_NOCACHE:
373
          #endif
374
          #ifdef F_NODIRECT                      /* macOS */
375
          case F_NODIRECT:
376
          #endif
377
0
          #ifdef F_NOTIFY                        /* Linux */
378
0
          case F_NOTIFY:
379
0
          #endif
380
          #ifdef F_RDAHEAD                       /* macOS */
381
          case F_RDAHEAD:
382
          #endif
383
          #ifdef F_SETBACKINGSTORE               /* macOS */
384
          case F_SETBACKINGSTORE:
385
          #endif
386
          #ifdef F_SETCONFINED                   /* macOS */
387
          case F_SETCONFINED:
388
          #endif
389
0
          #ifdef F_SETFD                         /* POSIX */
390
0
          case F_SETFD:
391
0
          #endif
392
0
          #ifdef F_SETFL                         /* POSIX */
393
3.88k
          case F_SETFL:
394
3.88k
          #endif
395
3.88k
          #ifdef F_SETLEASE                      /* Linux */
396
3.88k
          case F_SETLEASE:
397
3.88k
          #endif
398
          #ifdef F_SETNOSIGPIPE                  /* macOS */
399
          case F_SETNOSIGPIPE:
400
          #endif
401
3.88k
          #ifdef F_SETOWN                        /* POSIX */
402
3.88k
          case F_SETOWN:
403
3.88k
          #endif
404
3.88k
          #ifdef F_SETPIPE_SZ                    /* Linux */
405
3.88k
          case F_SETPIPE_SZ:
406
3.88k
          #endif
407
          #ifdef F_SETPROTECTIONCLASS            /* macOS */
408
          case F_SETPROTECTIONCLASS:
409
          #endif
410
3.88k
          #ifdef F_SETSIG                        /* Linux */
411
3.88k
          case F_SETSIG:
412
3.88k
          #endif
413
          #ifdef F_SINGLE_WRITER                 /* macOS */
414
          case F_SINGLE_WRITER:
415
          #endif
416
            /* These actions take an 'int' argument.  */
417
3.88k
            {
418
3.88k
              int x = va_arg (arg, int);
419
3.88k
              result = fcntl (fd, action, x);
420
3.88k
            }
421
3.88k
            break;
422
423
0
          default:
424
            /* Other actions take a pointer argument.  */
425
0
            {
426
0
              void *p = va_arg (arg, void *);
427
0
              result = fcntl (fd, action, p);
428
0
            }
429
0
            break;
430
7.77k
          }
431
#else
432
        errno = EINVAL;
433
#endif
434
7.77k
        break;
435
7.77k
      }
436
7.77k
    }
437
438
7.77k
  va_end (arg);
439
440
7.77k
  return result;
441
7.77k
}
442
443
static int
444
rpl_fcntl_DUPFD (int fd, int target)
445
0
{
446
0
  int result;
447
#if !HAVE_FCNTL
448
  result = dupfd (fd, target, 0);
449
#elif FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR
450
  /* Detect invalid target; needed for cygwin 1.5.x.  */
451
  if (target < 0 || getdtablesize () <= target)
452
    {
453
      result = -1;
454
      errno = EINVAL;
455
    }
456
  else
457
    {
458
      /* Haiku alpha 2 loses fd flags on original.  */
459
      int flags = fcntl (fd, F_GETFD);
460
      if (flags < 0)
461
        result = -1;
462
      else
463
        {
464
          result = fcntl (fd, F_DUPFD, target);
465
          if (0 <= result && fcntl (fd, F_SETFD, flags) == -1)
466
            {
467
              int saved_errno = errno;
468
              close (result);
469
              result = -1;
470
              errno = saved_errno;
471
            }
472
# if REPLACE_FCHDIR
473
          if (0 <= result)
474
            result = _gl_register_dup (fd, result);
475
# endif
476
        }
477
    }
478
#else
479
0
  result = fcntl (fd, F_DUPFD, target);
480
0
#endif
481
0
  return result;
482
0
}
483
484
static int
485
rpl_fcntl_DUPFD_CLOEXEC (int fd, int target)
486
0
{
487
0
  int result;
488
#if !HAVE_FCNTL
489
  result = dupfd (fd, target, O_CLOEXEC);
490
#else /* HAVE_FCNTL */
491
# if defined __NetBSD__ || defined __HAIKU__
492
  /* On NetBSD 9.0, the system fcntl (fd, F_DUPFD_CLOEXEC, target)
493
     has only the same effect as fcntl (fd, F_DUPFD, target).  */
494
  /* On Haiku, the system fcntl (fd, F_DUPFD_CLOEXEC, target) sets
495
     the FD_CLOEXEC flag on fd, not on target.  Therefore avoid the
496
     system fcntl in this case.  */
497
#  define have_dupfd_cloexec -1
498
# else
499
  /* Try the system call first, if the headers claim it exists
500
     (that is, if GNULIB_defined_F_DUPFD_CLOEXEC is 0), since we
501
     may be running with a glibc that has the macro but with an
502
     older kernel that does not support it.  Cache the
503
     information on whether the system call really works, but
504
     avoid caching failure if the corresponding F_DUPFD fails
505
     for any reason.  0 = unknown, 1 = yes, -1 = no.  */
506
0
  static int have_dupfd_cloexec = GNULIB_defined_F_DUPFD_CLOEXEC ? -1 : 0;
507
0
  if (0 <= have_dupfd_cloexec)
508
0
    {
509
0
      result = fcntl (fd, F_DUPFD_CLOEXEC, target);
510
0
      if (0 <= result || errno != EINVAL)
511
0
        {
512
0
          have_dupfd_cloexec = 1;
513
#  if REPLACE_FCHDIR
514
          if (0 <= result)
515
            result = _gl_register_dup (fd, result);
516
#  endif
517
0
        }
518
0
      else
519
0
        {
520
0
          result = rpl_fcntl_DUPFD (fd, target);
521
0
          if (result >= 0)
522
0
            have_dupfd_cloexec = -1;
523
0
        }
524
0
    }
525
0
  else
526
0
# endif
527
0
    result = rpl_fcntl_DUPFD (fd, target);
528
0
  if (0 <= result && have_dupfd_cloexec == -1)
529
0
    {
530
0
      int flags = fcntl (result, F_GETFD);
531
0
      if (flags < 0 || fcntl (result, F_SETFD, flags | FD_CLOEXEC) == -1)
532
0
        {
533
0
          int saved_errno = errno;
534
0
          close (result);
535
          errno = saved_errno;
536
0
          result = -1;
537
0
        }
538
0
    }
539
0
#endif /* HAVE_FCNTL */
540
0
  return result;
541
0
}
542
543
#undef fcntl
544
545
#ifdef __KLIBC__
546
static int
547
klibc_dupdirfd (int fd, int minfd)
548
{
549
  int tempfd = open ("NUL", O_RDONLY);
550
  if (tempfd == -1)
551
    return -1;
552
553
  if (tempfd >= minfd)
554
    {
555
      close (tempfd);
556
557
      char path[_MAX_PATH];
558
      if (__libc_Back_ioFHToPath (fd, path, sizeof (path)))
559
        return -1;
560
561
      int dupfd = open (path, O_RDONLY);
562
      if (dupfd == -1)
563
        return -1;
564
565
      if (dupfd >= minfd)
566
        return dupfd;
567
568
      /* Lower FD was closed by other threads. Fill again.  */
569
      tempfd = dupfd;
570
    }
571
572
  int dupfd = klibc_dupdirfd (fd, minfd);
573
574
  close (tempfd);
575
576
  return dupfd;
577
}
578
579
static int
580
klibc_fcntl (int fd, int action, /* arg */...)
581
{
582
  va_list arg_ptr;
583
  va_start (arg_ptr, action);
584
585
  int arg = va_arg (arg_ptr, int);
586
  int result = fcntl (fd, action, arg);
587
  /* EPERM for F_DUPFD, ENOTSUP for others */
588
  if (result == -1 && (errno == EPERM || errno == ENOTSUP))
589
    {
590
      struct stat sbuf;
591
      if (!fstat (fd, &sbuf) && S_ISDIR (sbuf.st_mode))
592
        {
593
          switch (action)
594
            {
595
            case F_DUPFD:
596
              result = klibc_dupdirfd (fd, arg);
597
              break;
598
599
            case F_GETFD:
600
              {
601
                PLIBCFH pFH = __libc_FH (fd);
602
                if (!pFH)
603
                  {
604
                    errno = EBADF;
605
                    break;
606
                }
607
608
                result = (pFH->fFlags & ((FD_CLOEXEC << __LIBC_FH_FDFLAGS_SHIFT )
609
                                         | O_NOINHERIT)) ? FD_CLOEXEC : 0;
610
              }
611
              break;
612
613
            case F_SETFD:
614
              {
615
                if (arg & ~FD_CLOEXEC)
616
                  break;
617
618
                PLIBCFH pFH = __libc_FH (fd);
619
                if (!pFH)
620
                  {
621
                    errno = EBADF;
622
                    break;
623
                  }
624
625
                unsigned fFlags = pFH->fFlags;
626
                if (arg & FD_CLOEXEC)
627
                  fFlags |= (FD_CLOEXEC << __LIBC_FH_FDFLAGS_SHIFT) | O_NOINHERIT;
628
                else
629
                  fFlags &= ~((FD_CLOEXEC << __LIBC_FH_FDFLAGS_SHIFT) | O_NOINHERIT);
630
631
                result = __libc_FHSetFlags (pFH, fd, fFlags);
632
                if (result < 0)
633
                  {
634
                    errno = -result;
635
                    result = -1;
636
                  }
637
              }
638
              break;
639
640
            case F_GETFL:
641
              result = 0;
642
              break;
643
644
            case F_SETFL:
645
              if (arg != 0)
646
                break;
647
648
              result = 0;
649
              break;
650
651
            default:
652
              errno = EINVAL;
653
              break;
654
            }
655
        }
656
    }
657
658
  va_end (arg_ptr);
659
660
  return result;
661
}
662
663
#endif