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