xref: /illumos-gate/usr/src/test/libc-tests/tests/stdio/fdclose.c (revision b1c37be59f1fe13f2e54002b32bc505358776e9f)
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