1 /* Tests for opening a file without destroying an old file with the same name.
2
3 Copyright (C) 2020-2021 Free Software Foundation, Inc.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17
18 /* Written by Bruno Haible, 2020. */
19
20 static void
test_open_supersede(bool supersede_if_exists,bool supersede_if_does_not_exist)21 test_open_supersede (bool supersede_if_exists, bool supersede_if_does_not_exist)
22 {
23 char xtemplate[] = "gnulibtestXXXXXX";
24 char *dir = mkdtemp (xtemplate);
25 char *filename = file_name_concat (dir, "test.mo", NULL);
26 struct stat statbuf;
27
28 /* Test the case that the file does not yet exist. */
29 {
30 ASSERT (stat (filename, &statbuf) < 0);
31
32 struct supersede_final_action action;
33 int fd = open_supersede (filename, O_RDWR | O_BINARY | O_TRUNC, 0666,
34 supersede_if_exists, supersede_if_does_not_exist,
35 &action);
36 ASSERT (fd >= 0);
37 ASSERT (write (fd, "Hello world\n", 12) == 12);
38 if (supersede_if_does_not_exist)
39 ASSERT (stat (filename, &statbuf) < 0);
40 else
41 ASSERT (stat (filename, &statbuf) == 0);
42 ASSERT (close_supersede (fd, &action) == 0);
43
44 ASSERT (stat (filename, &statbuf) == 0);
45
46 size_t file_size;
47 char *file_contents = read_file (filename, RF_BINARY, &file_size);
48 ASSERT (file_size == 12);
49 ASSERT (memcmp (file_contents, "Hello world\n", 12) == 0);
50 }
51
52 /* Test the case that the file exists and is a regular file. */
53 {
54 ASSERT (stat (filename, &statbuf) == 0);
55 dev_t orig_dev = statbuf.st_dev;
56 ino_t orig_ino = statbuf.st_ino;
57
58 struct supersede_final_action action;
59 int fd = open_supersede (filename, O_RDWR | O_BINARY | O_TRUNC, 0666,
60 supersede_if_exists, supersede_if_does_not_exist,
61 &action);
62 ASSERT (fd >= 0);
63 ASSERT (write (fd, "Foobar\n", 7) == 7);
64 ASSERT (stat (filename, &statbuf) == 0);
65 {
66 size_t file_size;
67 char *file_contents = read_file (filename, RF_BINARY, &file_size);
68 if (supersede_if_exists)
69 {
70 ASSERT (file_size == 12);
71 ASSERT (memcmp (file_contents, "Hello world\n", 12) == 0);
72 }
73 else
74 {
75 ASSERT (file_size == 7);
76 ASSERT (memcmp (file_contents, "Foobar\n", 7) == 0);
77 }
78 }
79 ASSERT (close_supersede (fd, &action) == 0);
80
81 ASSERT (stat (filename, &statbuf) == 0);
82
83 size_t file_size;
84 char *file_contents = read_file (filename, RF_BINARY, &file_size);
85 ASSERT (file_size == 7);
86 ASSERT (memcmp (file_contents, "Foobar\n", 7) == 0);
87
88 if (supersede_if_exists)
89 {
90 /* Verify that the file now has a different inode number, on the same
91 device. */
92 #if !(defined _WIN32 && !defined __CYGWIN__)
93 /* Note: On Linux/mips, statbuf.st_dev is smaller than a dev_t! */
94 dev_t new_dev = statbuf.st_dev;
95 ASSERT (memcmp (&orig_dev, &new_dev, sizeof (dev_t)) == 0);
96 ASSERT (memcmp (&orig_ino, &statbuf.st_ino, sizeof (ino_t)) != 0);
97 #endif
98 }
99 }
100
101 /* Test the case that the file exists and is a character device. */
102 {
103 ASSERT (stat (DEV_NULL, &statbuf) == 0);
104
105 struct supersede_final_action action;
106 int fd = open_supersede (DEV_NULL, O_RDWR | O_BINARY | O_TRUNC, 0666,
107 supersede_if_exists, supersede_if_does_not_exist,
108 &action);
109 ASSERT (fd >= 0);
110 ASSERT (write (fd, "Foobar\n", 7) == 7);
111 ASSERT (stat (DEV_NULL, &statbuf) == 0);
112 ASSERT (close_supersede (fd, &action) == 0);
113
114 ASSERT (stat (DEV_NULL, &statbuf) == 0);
115 }
116
117 /* Test the case that the file is a symbolic link to an existing regular
118 file. */
119 {
120 const char *linkname = "link1";
121 unlink (linkname);
122 if (symlink (filename, linkname) >= 0)
123 {
124 ASSERT (stat (linkname, &statbuf) == 0);
125 dev_t orig_dev = statbuf.st_dev;
126 ino_t orig_ino = statbuf.st_ino;
127
128 struct supersede_final_action action;
129 int fd =
130 open_supersede (linkname, O_RDWR | O_BINARY | O_TRUNC, 0666,
131 supersede_if_exists, supersede_if_does_not_exist,
132 &action);
133 ASSERT (fd >= 0);
134 ASSERT (write (fd, "New\n", 4) == 4);
135 ASSERT (stat (linkname, &statbuf) == 0);
136 {
137 size_t file_size;
138 char *file_contents = read_file (linkname, RF_BINARY, &file_size);
139 if (supersede_if_exists)
140 {
141 ASSERT (file_size == 7);
142 ASSERT (memcmp (file_contents, "Foobar\n", 7) == 0);
143 }
144 else
145 {
146 ASSERT (file_size == 4);
147 ASSERT (memcmp (file_contents, "New\n", 4) == 0);
148 }
149 }
150 ASSERT (close_supersede (fd, &action) == 0);
151
152 ASSERT (stat (linkname, &statbuf) == 0);
153
154 size_t file_size;
155 char *file_contents = read_file (linkname, RF_BINARY, &file_size);
156 ASSERT (file_size == 4);
157 ASSERT (memcmp (file_contents, "New\n", 4) == 0);
158
159 if (supersede_if_exists)
160 {
161 /* Verify that the file now has a different inode number, on the
162 same device. */
163 #if !(defined _WIN32 && !defined __CYGWIN__)
164 /* Note: On Linux/mips, statbuf.st_dev is smaller than a dev_t! */
165 dev_t new_dev = statbuf.st_dev;
166 ASSERT (memcmp (&orig_dev, &new_dev, sizeof (dev_t)) == 0);
167 ASSERT (memcmp (&orig_ino, &statbuf.st_ino, sizeof (ino_t)) != 0);
168 #endif
169 }
170
171 /* Clean up. */
172 unlink (linkname);
173 }
174 }
175
176 /* Test the case that the file is a symbolic link to an existing character
177 device. */
178 {
179 const char *linkname = "link2";
180 unlink (linkname);
181 if (symlink (DEV_NULL, linkname) >= 0)
182 {
183 ASSERT (stat (linkname, &statbuf) == 0);
184
185 struct supersede_final_action action;
186 int fd =
187 open_supersede (linkname, O_RDWR | O_BINARY | O_TRUNC, 0666,
188 supersede_if_exists, supersede_if_does_not_exist,
189 &action);
190 ASSERT (fd >= 0);
191 ASSERT (write (fd, "New\n", 4) == 4);
192 ASSERT (stat (linkname, &statbuf) == 0);
193 ASSERT (close_supersede (fd, &action) == 0);
194
195 ASSERT (stat (linkname, &statbuf) == 0);
196
197 /* Clean up. */
198 unlink (linkname);
199 }
200 }
201
202 /* Clean up. */
203 unlink (filename);
204
205 /* Test the case that the file is a symbolic link to a nonexistent file in an
206 existing directory. */
207 {
208 const char *linkname = "link3";
209 unlink (linkname);
210 if (symlink (filename, linkname) >= 0)
211 {
212 ASSERT (stat (linkname, &statbuf) < 0);
213
214 struct supersede_final_action action;
215 int fd =
216 open_supersede (linkname, O_RDWR | O_BINARY | O_TRUNC, 0666,
217 supersede_if_exists, supersede_if_does_not_exist,
218 &action);
219 ASSERT (fd >= 0);
220 ASSERT (write (fd, "Hello world\n", 12) == 12);
221 if (supersede_if_does_not_exist)
222 ASSERT (stat (linkname, &statbuf) < 0);
223 else
224 ASSERT (stat (linkname, &statbuf) == 0);
225 ASSERT (close_supersede (fd, &action) == 0);
226
227 ASSERT (stat (linkname, &statbuf) == 0);
228
229 size_t file_size;
230 char *file_contents = read_file (linkname, RF_BINARY, &file_size);
231 ASSERT (file_size == 12);
232 ASSERT (memcmp (file_contents, "Hello world\n", 12) == 0);
233
234 /* Clean up. */
235 unlink (linkname);
236 }
237 }
238
239 /* Test the case that the file is a symbolic link to a nonexistent file in a
240 nonexistent directory. */
241 {
242 const char *linkname = "link4";
243 unlink (linkname);
244 if (symlink ("/nonexistent/gnulibtest8237/24715863701440", linkname) >= 0)
245 {
246 ASSERT (stat (linkname, &statbuf) < 0);
247
248 struct supersede_final_action action;
249 int fd =
250 open_supersede (linkname, O_RDWR | O_BINARY | O_TRUNC, 0666,
251 supersede_if_exists, supersede_if_does_not_exist,
252 &action);
253 ASSERT (fd < 0);
254 ASSERT (errno == ENOENT);
255
256 ASSERT (stat (linkname, &statbuf) < 0);
257
258 /* Clean up. */
259 unlink (linkname);
260 }
261 }
262
263 /* Clean up. */
264 unlink (filename);
265 rmdir (dir);
266 }
267