The Pedigree Project 0.1
dup3.c
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#define _GNU_SOURCE
9
10#include <errno.h>
11#include <fcntl.h>
12#include <pthread.h>
13#include <sched.h>
14#include <signal.h>
15#include <stdio.h>
16#include <unistd.h>
17
18#include <sys/syscall.h>
19
20extern void fail(void) __attribute__((noreturn));
21
22enum { Dup3StressIterations = 256, Dup3WaitAttempts = 100000 };
23
25 int source;
26 int target;
27 volatile int ready;
28 volatile int start;
29 volatile int finished;
30 volatile int failed;
31 volatile int observations;
32};
33
34static int wait_for_value(volatile int* value, int expected) {
35 for (size_t attempt = 0; attempt < Dup3WaitAttempts; ++attempt) {
36 if (__atomic_load_n(value, __ATOMIC_ACQUIRE) == expected)
37 return 0;
38 sched_yield();
39 }
40 return -1;
41}
42
43static void* replace_cloexec_descriptor(void* parameter) {
44 struct dup3_stress_context* context = parameter;
45 __atomic_add_fetch(&context->ready, 1, __ATOMIC_RELEASE);
46 if (wait_for_value(&context->start, 1)) {
47 __atomic_store_n(&context->failed, 1, __ATOMIC_RELEASE);
48 __atomic_store_n(&context->finished, 1, __ATOMIC_RELEASE);
49 return 0;
50 }
51
52 for (size_t iteration = 0; iteration < Dup3StressIterations; ++iteration) {
53 if (syscall(SYS_dup3, context->source, context->target, O_CLOEXEC) != context->target) {
54 __atomic_store_n(&context->failed, 1, __ATOMIC_RELEASE);
55 break;
56 }
57 sched_yield();
58 }
59 __atomic_store_n(&context->finished, 1, __ATOMIC_RELEASE);
60 return 0;
61}
62
63static void* observe_cloexec_descriptor(void* parameter) {
64 struct dup3_stress_context* context = parameter;
65 __atomic_add_fetch(&context->ready, 1, __ATOMIC_RELEASE);
66 if (wait_for_value(&context->start, 1)) {
67 __atomic_store_n(&context->failed, 1, __ATOMIC_RELEASE);
68 return 0;
69 }
70
71 do {
72 if (fcntl(context->target, F_GETFD) != FD_CLOEXEC) {
73 __atomic_store_n(&context->failed, 1, __ATOMIC_RELEASE);
74 return 0;
75 }
76 __atomic_add_fetch(&context->observations, 1, __ATOMIC_RELEASE);
77 sched_yield();
78 } while (!__atomic_load_n(&context->finished, __ATOMIC_ACQUIRE));
79 return 0;
80}
81
82void test_dup3(void) {
83 int sourcePipe[2];
84 int displacedPipe[2];
85 void (*previousSigpipe)(int) = signal(SIGPIPE, SIG_IGN);
86
87 puts("Testing dup3 descriptor replacement... ");
88 fflush(stdout);
89 if (previousSigpipe == SIG_ERR || pipe(sourcePipe) || pipe(displacedPipe) ||
90 fcntl(sourcePipe[0], F_SETFD, FD_CLOEXEC))
91 fail();
92
93 errno = 173;
94 if (syscall(SYS_dup3, sourcePipe[0], displacedPipe[0], 0) != displacedPipe[0] || errno != 173 ||
95 fcntl(sourcePipe[0], F_GETFD) != FD_CLOEXEC || fcntl(displacedPipe[0], F_GETFD) != 0)
96 fail();
97
98 errno = 0;
99 if (write(displacedPipe[1], "x", 1) != -1 || errno != EPIPE || close(displacedPipe[1]))
100 fail();
101
102 if (write(sourcePipe[1], "a", 1) != 1)
103 fail();
104 char byte = 0;
105 if (read(displacedPipe[0], &byte, 1) != 1 || byte != 'a')
106 fail();
107
108 if (syscall(SYS_dup3, sourcePipe[0], displacedPipe[0], O_CLOEXEC) != displacedPipe[0] ||
109 fcntl(displacedPipe[0], F_GETFD) != FD_CLOEXEC)
110 fail();
111
112 errno = 0;
113 if (syscall(SYS_dup3, sourcePipe[0], sourcePipe[0], 0) != -1 || errno != EINVAL ||
114 fcntl(sourcePipe[0], F_GETFD) != FD_CLOEXEC)
115 fail();
116 errno = 0;
117 if (syscall(SYS_dup3, -1, -1, 0) != -1 || errno != EINVAL)
118 fail();
119 errno = 0;
120 if (syscall(SYS_dup3, sourcePipe[0], displacedPipe[0], O_NONBLOCK) != -1 || errno != EINVAL ||
121 fcntl(displacedPipe[0], F_GETFD) != FD_CLOEXEC)
122 fail();
123 errno = 0;
124 if (syscall(SYS_dup3, -1, displacedPipe[0], 0) != -1 || errno != EBADF ||
125 fcntl(displacedPipe[0], F_GETFD) != FD_CLOEXEC)
126 fail();
127 errno = 0;
128 if (syscall(SYS_dup3, sourcePipe[0], -1, 0) != -1 || errno != EBADF)
129 fail();
130 errno = 0;
131 if (syscall(SYS_dup3, sourcePipe[0], 16384, 0) != -1 || errno != EBADF)
132 fail();
133
134 if (write(sourcePipe[1], "b", 1) != 1 || read(displacedPipe[0], &byte, 1) != 1 || byte != 'b')
135 fail();
136
137 struct dup3_stress_context stress = {
138 .source = sourcePipe[0],
139 .target = displacedPipe[0],
140 };
141 pthread_t replacer;
142 pthread_t observer;
143 if (pthread_create(&replacer, 0, replace_cloexec_descriptor, &stress))
144 fail();
145 if (pthread_create(&observer, 0, observe_cloexec_descriptor, &stress)) {
146 __atomic_store_n(&stress.start, 1, __ATOMIC_RELEASE);
147 pthread_join(replacer, 0);
148 fail();
149 }
150 if (wait_for_value(&stress.ready, 2)) {
151 __atomic_store_n(&stress.start, 1, __ATOMIC_RELEASE);
152 pthread_join(replacer, 0);
153 pthread_join(observer, 0);
154 fail();
155 }
156 __atomic_store_n(&stress.start, 1, __ATOMIC_RELEASE);
157 if (pthread_join(replacer, 0) || pthread_join(observer, 0) ||
158 __atomic_load_n(&stress.failed, __ATOMIC_ACQUIRE) ||
159 !__atomic_load_n(&stress.observations, __ATOMIC_ACQUIRE))
160 fail();
161
162 if (close(sourcePipe[0]) || write(sourcePipe[1], "c", 1) != 1 ||
163 read(displacedPipe[0], &byte, 1) != 1 || byte != 'c' || close(displacedPipe[0]) ||
164 close(sourcePipe[1]) || signal(SIGPIPE, previousSigpipe) == SIG_ERR)
165 fail();
166
167 puts("OK\n");
168 fflush(stdout);
169}