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