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 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 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