1*b1c37be5SHans Rosenfeld /*
2*b1c37be5SHans Rosenfeld * This file and its contents are supplied under the terms of the
3*b1c37be5SHans Rosenfeld * Common Development and Distribution License ("CDDL"), version 1.0.
4*b1c37be5SHans Rosenfeld * You may only use this file in accordance with the terms of version
5*b1c37be5SHans Rosenfeld * 1.0 of the CDDL.
6*b1c37be5SHans Rosenfeld *
7*b1c37be5SHans Rosenfeld * A full copy of the text of the CDDL should have accompanied this
8*b1c37be5SHans Rosenfeld * source. A copy of the CDDL is also available via the Internet at
9*b1c37be5SHans Rosenfeld * http://www.illumos.org/license/CDDL.
10*b1c37be5SHans Rosenfeld */
11*b1c37be5SHans Rosenfeld
12*b1c37be5SHans Rosenfeld /*
13*b1c37be5SHans Rosenfeld * Copyright 2025 Hans Rosenfeld
14*b1c37be5SHans Rosenfeld */
15*b1c37be5SHans Rosenfeld
16*b1c37be5SHans Rosenfeld #include <sys/types.h>
17*b1c37be5SHans Rosenfeld #include <sys/stat.h>
18*b1c37be5SHans Rosenfeld #include <fcntl.h>
19*b1c37be5SHans Rosenfeld #include <stdio.h>
20*b1c37be5SHans Rosenfeld #include <stdlib.h>
21*b1c37be5SHans Rosenfeld #include <strings.h>
22*b1c37be5SHans Rosenfeld #include <unistd.h>
23*b1c37be5SHans Rosenfeld #include <errno.h>
24*b1c37be5SHans Rosenfeld #include <err.h>
25*b1c37be5SHans Rosenfeld #include <limits.h>
26*b1c37be5SHans Rosenfeld
27*b1c37be5SHans Rosenfeld static int
fdclose_test(char * ident,FILE * fp,int exp_ret,int exp_fd,int exp_errno)28*b1c37be5SHans Rosenfeld fdclose_test(char *ident, FILE *fp, int exp_ret, int exp_fd, int exp_errno)
29*b1c37be5SHans Rosenfeld {
30*b1c37be5SHans Rosenfeld int fd = INT_MIN;
31*b1c37be5SHans Rosenfeld int ret = 0;
32*b1c37be5SHans Rosenfeld
33*b1c37be5SHans Rosenfeld errno = 0;
34*b1c37be5SHans Rosenfeld ret = fdclose(fp, &fd);
35*b1c37be5SHans Rosenfeld if (ret != exp_ret)
36*b1c37be5SHans Rosenfeld err(EXIT_FAILURE, "%s unexpected result %d (expected %d)",
37*b1c37be5SHans Rosenfeld ident, ret, exp_ret);
38*b1c37be5SHans Rosenfeld
39*b1c37be5SHans Rosenfeld if (fd != exp_fd)
40*b1c37be5SHans Rosenfeld err(EXIT_FAILURE, "%s unexpected fd %d (expected %d)",
41*b1c37be5SHans Rosenfeld ident, fd, exp_fd);
42*b1c37be5SHans Rosenfeld
43*b1c37be5SHans Rosenfeld if (errno != exp_errno)
44*b1c37be5SHans Rosenfeld errx(EXIT_FAILURE, "%s unexpected errno %s (expected %s)",
45*b1c37be5SHans Rosenfeld ident, strerrorname_np(errno), strerrorname_np(exp_errno));
46*b1c37be5SHans Rosenfeld
47*b1c37be5SHans Rosenfeld return (fd);
48*b1c37be5SHans Rosenfeld }
49*b1c37be5SHans Rosenfeld
50*b1c37be5SHans Rosenfeld int
main(int argc,char ** argv)51*b1c37be5SHans Rosenfeld main(int argc, char **argv)
52*b1c37be5SHans Rosenfeld {
53*b1c37be5SHans Rosenfeld int ret = 0;
54*b1c37be5SHans Rosenfeld char buf[33];
55*b1c37be5SHans Rosenfeld int fd;
56*b1c37be5SHans Rosenfeld FILE *devnull;
57*b1c37be5SHans Rosenfeld FILE *mem;
58*b1c37be5SHans Rosenfeld struct stat stat;
59*b1c37be5SHans Rosenfeld ino_t ino;
60*b1c37be5SHans Rosenfeld
61*b1c37be5SHans Rosenfeld /*
62*b1c37be5SHans Rosenfeld * Use /dev/null for our first tests. Open it as a file, then fstat it
63*b1c37be5SHans Rosenfeld * and get the underlying inode.
64*b1c37be5SHans Rosenfeld */
65*b1c37be5SHans Rosenfeld fd = open("/dev/null", O_WRONLY);
66*b1c37be5SHans Rosenfeld if (fd == -1)
67*b1c37be5SHans Rosenfeld err(EXIT_FAILURE, "open(/dev/null)");
68*b1c37be5SHans Rosenfeld
69*b1c37be5SHans Rosenfeld if (fstat(fd, &stat) == -1)
70*b1c37be5SHans Rosenfeld err(EXIT_FAILURE, "fstat(/dev/null) after open()");
71*b1c37be5SHans Rosenfeld
72*b1c37be5SHans Rosenfeld ino = stat.st_ino;
73*b1c37be5SHans Rosenfeld
74*b1c37be5SHans Rosenfeld /*
75*b1c37be5SHans Rosenfeld * Now, fdopen() this to get a FILE to work with. Make sure the FILE
76*b1c37be5SHans Rosenfeld * is backed by the right fd.
77*b1c37be5SHans Rosenfeld */
78*b1c37be5SHans Rosenfeld devnull = fdopen(fd, "w");
79*b1c37be5SHans Rosenfeld if (devnull == NULL)
80*b1c37be5SHans Rosenfeld err(EXIT_FAILURE, "fdopen(/dev/null)");
81*b1c37be5SHans Rosenfeld
82*b1c37be5SHans Rosenfeld if (fileno(devnull) != fd)
83*b1c37be5SHans Rosenfeld err(EXIT_FAILURE, "fileno(/dev/null): unexpected fd %d "
84*b1c37be5SHans Rosenfeld "(expected %d)", fileno(devnull), fd);
85*b1c37be5SHans Rosenfeld
86*b1c37be5SHans Rosenfeld /*
87*b1c37be5SHans Rosenfeld * Verify fdclose() works on a FILE which is already open.
88*b1c37be5SHans Rosenfeld */
89*b1c37be5SHans Rosenfeld fd = fdclose_test("fdclose(/dev/null)", devnull, 0, fd, 0);
90*b1c37be5SHans Rosenfeld
91*b1c37be5SHans Rosenfeld /*
92*b1c37be5SHans Rosenfeld * Verify that the underlying file descriptor hasn't changed.
93*b1c37be5SHans Rosenfeld */
94*b1c37be5SHans Rosenfeld if (fileno(devnull) != fd)
95*b1c37be5SHans Rosenfeld err(EXIT_FAILURE, "fileno(/dev/null): unexpected fd %d "
96*b1c37be5SHans Rosenfeld "(expected %d)", fileno(devnull), fd);
97*b1c37be5SHans Rosenfeld
98*b1c37be5SHans Rosenfeld /*
99*b1c37be5SHans Rosenfeld * Verify that we can still fstat() the file descriptor, and that the
100*b1c37be5SHans Rosenfeld * inode hasn't changed.
101*b1c37be5SHans Rosenfeld */
102*b1c37be5SHans Rosenfeld if (fstat(fd, &stat) == -1)
103*b1c37be5SHans Rosenfeld err(EXIT_FAILURE, "fstat(/dev/null) after fdclose()");
104*b1c37be5SHans Rosenfeld
105*b1c37be5SHans Rosenfeld if (ino != stat.st_ino)
106*b1c37be5SHans Rosenfeld errx(EXIT_FAILURE, "/dev/null inode changed after fdclose(): "
107*b1c37be5SHans Rosenfeld "%ld (expected %ld)", stat.st_ino, ino);
108*b1c37be5SHans Rosenfeld
109*b1c37be5SHans Rosenfeld /*
110*b1c37be5SHans Rosenfeld * Calling fdclose() again on a closed FILE should return EOF without
111*b1c37be5SHans Rosenfeld * setting errno. It should also return the file descriptor. This is
112*b1c37be5SHans Rosenfeld * decidedly not part of the interface specification, it's only an
113*b1c37be5SHans Rosenfeld * implementation detail of fdclose() and fclose_helper().
114*b1c37be5SHans Rosenfeld */
115*b1c37be5SHans Rosenfeld fd = fdclose_test("2nd fdclose(/dev/null)", devnull, EOF, fd, 0);
116*b1c37be5SHans Rosenfeld
117*b1c37be5SHans Rosenfeld /*
118*b1c37be5SHans Rosenfeld * Verify the FILE is indeed closed by writing to it, which should
119*b1c37be5SHans Rosenfeld * return EOF. This is an illumos-specific implementation detail,
120*b1c37be5SHans Rosenfeld * what fputs() or any other stdio function would do on a closed FILE
121*b1c37be5SHans Rosenfeld * is undefined behaviour.
122*b1c37be5SHans Rosenfeld */
123*b1c37be5SHans Rosenfeld ret = fputs("Hello World\n", devnull);
124*b1c37be5SHans Rosenfeld if (ret != EOF)
125*b1c37be5SHans Rosenfeld errx(1, "unexpected result from fputs(\"Hello World\\n\") "
126*b1c37be5SHans Rosenfeld "on closed FILE, ret = %d", ret);
127*b1c37be5SHans Rosenfeld
128*b1c37be5SHans Rosenfeld /*
129*b1c37be5SHans Rosenfeld * Verify that the underlying file descriptor is still open for writing,
130*b1c37be5SHans Rosenfeld * so this should not fail.
131*b1c37be5SHans Rosenfeld */
132*b1c37be5SHans Rosenfeld ret = write(fd, "Goodbye Cruel World\n", 20);
133*b1c37be5SHans Rosenfeld if (ret < 0)
134*b1c37be5SHans Rosenfeld err(1, "write(/dev/null) failed");
135*b1c37be5SHans Rosenfeld
136*b1c37be5SHans Rosenfeld /*
137*b1c37be5SHans Rosenfeld * Close /dev/null, we're done with it. Try to fstat() it again, which
138*b1c37be5SHans Rosenfeld * now should fail.
139*b1c37be5SHans Rosenfeld */
140*b1c37be5SHans Rosenfeld (void) close(fd);
141*b1c37be5SHans Rosenfeld
142*b1c37be5SHans Rosenfeld ret = fstat(fd, &stat);
143*b1c37be5SHans Rosenfeld if (ret != -1)
144*b1c37be5SHans Rosenfeld errx(EXIT_FAILURE, "unexpected result from fstat(/dev/null) "
145*b1c37be5SHans Rosenfeld "after close(), ret = %d", ret);
146*b1c37be5SHans Rosenfeld
147*b1c37be5SHans Rosenfeld /*
148*b1c37be5SHans Rosenfeld * Verify fdclose() works as expected on a memory stream: The stream is
149*b1c37be5SHans Rosenfeld * closed, the fd returned is -1, and errno is ENOTSUP.
150*b1c37be5SHans Rosenfeld */
151*b1c37be5SHans Rosenfeld bzero(buf, sizeof (buf));
152*b1c37be5SHans Rosenfeld mem = fmemopen(buf, sizeof (buf), "w");
153*b1c37be5SHans Rosenfeld if (mem == NULL)
154*b1c37be5SHans Rosenfeld err(1, "fmemopen() failed");
155*b1c37be5SHans Rosenfeld
156*b1c37be5SHans Rosenfeld ret = fputs("Hello World\n", mem);
157*b1c37be5SHans Rosenfeld if (ret == EOF)
158*b1c37be5SHans Rosenfeld err(1, "fputs(mem) failed");
159*b1c37be5SHans Rosenfeld
160*b1c37be5SHans Rosenfeld fd = fdclose_test("fdclose(mem)", mem, EOF, -1, ENOTSUP);
161*b1c37be5SHans Rosenfeld
162*b1c37be5SHans Rosenfeld ret = fputs("Goodbye Cruel World\n", mem);
163*b1c37be5SHans Rosenfeld if (ret != EOF)
164*b1c37be5SHans Rosenfeld errx(1, "fputs(..., mem) on closed FILE, ret = %d "
165*b1c37be5SHans Rosenfeld "(expected EOF)", ret);
166*b1c37be5SHans Rosenfeld
167*b1c37be5SHans Rosenfeld /*
168*b1c37be5SHans Rosenfeld * Verify that only the string successfully written into the FILE ended
169*b1c37be5SHans Rosenfeld * up in the buffer.
170*b1c37be5SHans Rosenfeld */
171*b1c37be5SHans Rosenfeld if (strcmp(buf, "Hello World\n") != 0)
172*b1c37be5SHans Rosenfeld errx(1, "mem contents unexpected: %s\n"
173*b1c37be5SHans Rosenfeld "expected: %s", buf, "Hello World\n");
174*b1c37be5SHans Rosenfeld
175*b1c37be5SHans Rosenfeld printf("tests passed\n");
176*b1c37be5SHans Rosenfeld return (0);
177*b1c37be5SHans Rosenfeld }
178