1 /*
2 * Copyright (C) 2013 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see
16 * <http://www.gnu.org/licenses/>.
17 */
18
19 #include <config.h>
20
21 #include <fcntl.h>
22 #include <unistd.h>
23
24 #include "testutils.h"
25 #include "virfile.h"
26 #include "virstring.h"
27
28 #ifdef __linux__
29 # include <linux/falloc.h>
30 #endif
31
32 #define VIR_FROM_THIS VIR_FROM_NONE
33
34 #if defined WITH_MNTENT_H && defined WITH_GETMNTENT_R
testFileCheckMounts(const char * prefix,char ** gotmounts,size_t gotnmounts,const char * const * wantmounts,size_t wantnmounts)35 static int testFileCheckMounts(const char *prefix,
36 char **gotmounts,
37 size_t gotnmounts,
38 const char *const*wantmounts,
39 size_t wantnmounts)
40 {
41 size_t i;
42 if (gotnmounts != wantnmounts) {
43 fprintf(stderr, "Expected %zu mounts under %s, but got %zu\n",
44 wantnmounts, prefix, gotnmounts);
45 return -1;
46 }
47 for (i = 0; i < gotnmounts; i++) {
48 if (STRNEQ(gotmounts[i], wantmounts[i])) {
49 fprintf(stderr, "Expected mount[%zu] '%s' but got '%s'\n",
50 i, wantmounts[i], gotmounts[i]);
51 return -1;
52 }
53 }
54 return 0;
55 }
56
57 struct testFileGetMountSubtreeData {
58 const char *path;
59 const char *prefix;
60 const char *const *mounts;
61 size_t nmounts;
62 bool rev;
63 };
64
testFileGetMountSubtree(const void * opaque)65 static int testFileGetMountSubtree(const void *opaque)
66 {
67 int ret = -1;
68 g_auto(GStrv) gotmounts = NULL;
69 size_t gotnmounts = 0;
70 const struct testFileGetMountSubtreeData *data = opaque;
71
72 if (data->rev) {
73 if (virFileGetMountReverseSubtree(data->path,
74 data->prefix,
75 &gotmounts,
76 &gotnmounts) < 0)
77 goto cleanup;
78 } else {
79 if (virFileGetMountSubtree(data->path,
80 data->prefix,
81 &gotmounts,
82 &gotnmounts) < 0)
83 goto cleanup;
84 }
85
86 ret = testFileCheckMounts(data->prefix,
87 gotmounts, gotnmounts,
88 data->mounts, data->nmounts);
89
90 cleanup:
91 return ret;
92 }
93 #endif /* ! defined WITH_MNTENT_H && defined WITH_GETMNTENT_R */
94
95 struct testFileSanitizePathData
96 {
97 const char *path;
98 const char *expect;
99 };
100
101 static int
testFileSanitizePath(const void * opaque)102 testFileSanitizePath(const void *opaque)
103 {
104 const struct testFileSanitizePathData *data = opaque;
105 g_autofree char *actual = NULL;
106
107 if (!(actual = virFileSanitizePath(data->path)))
108 return -1;
109
110 if (STRNEQ(actual, data->expect)) {
111 fprintf(stderr, "\nexpect: '%s'\nactual: '%s'\n", data->expect, actual);
112 return -1;
113 }
114
115 return 0;
116 }
117
118
119 #if WITH_DECL_SEEK_HOLE && defined(__linux__)
120
121 /* Create a sparse file. @offsets in KiB. */
122 static int
makeSparseFile(const off_t offsets[],const bool startData)123 makeSparseFile(const off_t offsets[],
124 const bool startData)
125 {
126 int fd = -1;
127 char path[] = abs_builddir "fileInData.XXXXXX";
128 off_t len = 0;
129 size_t i;
130
131 if ((fd = g_mkstemp_full(path, O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR)) < 0)
132 goto error;
133
134 if (unlink(path) < 0)
135 goto error;
136
137 for (i = 0; offsets[i] != (off_t) -1; i++)
138 len += offsets[i] * 1024;
139
140 while (len) {
141 const char buf[] = "abcdefghijklmnopqrstuvwxyz";
142 off_t toWrite = sizeof(buf);
143
144 if (toWrite > len)
145 toWrite = len;
146
147 if (safewrite(fd, buf, toWrite) < 0) {
148 fprintf(stderr, "unable to write to %s (errno=%d)\n", path, errno);
149 goto error;
150 }
151
152 len -= toWrite;
153 }
154
155 len = 0;
156 for (i = 0; offsets[i] != (off_t) -1; i++) {
157 bool inData = startData;
158
159 if (i % 2)
160 inData = !inData;
161
162 if (!inData &&
163 fallocate(fd,
164 FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
165 len, offsets[i] * 1024) < 0) {
166 fprintf(stderr, "unable to punch a hole at offset %lld length %lld\n",
167 (long long) len, (long long) offsets[i]);
168 goto error;
169 }
170
171 len += offsets[i] * 1024;
172 }
173
174 if (lseek(fd, 0, SEEK_SET) == (off_t) -1) {
175 fprintf(stderr, "unable to lseek (errno=%d)\n", errno);
176 goto error;
177 }
178
179 return fd;
180 error:
181 VIR_FORCE_CLOSE(fd);
182 return -1;
183 }
184
185
186 # define EXTENT 4
187 static bool
holesSupported(void)188 holesSupported(void)
189 {
190 off_t offsets[] = {EXTENT, EXTENT, EXTENT, -1};
191 off_t tmp;
192 VIR_AUTOCLOSE fd = -1;
193
194 if ((fd = makeSparseFile(offsets, true)) < 0)
195 return false;
196
197 /* The way this works is: there are 4K of data followed by 4K hole followed
198 * by 4K hole again. Check if the filesystem we are running the test suite
199 * on supports holes. */
200 if ((tmp = lseek(fd, 0, SEEK_DATA)) == (off_t) -1)
201 return false;
202
203 if (tmp != 0)
204 return false;
205
206 if ((tmp = lseek(fd, tmp, SEEK_HOLE)) == (off_t) -1)
207 return false;
208
209 if (tmp != EXTENT * 1024)
210 return false;
211
212 if ((tmp = lseek(fd, tmp, SEEK_DATA)) == (off_t) -1)
213 return false;
214
215 if (tmp != 2 * EXTENT * 1024)
216 return false;
217
218 if ((tmp = lseek(fd, tmp, SEEK_HOLE)) == (off_t) -1)
219 return false;
220
221 if (tmp != 3 * EXTENT * 1024)
222 return false;
223
224 return true;
225 }
226
227 #else /* !WITH_DECL_SEEK_HOLE || !defined(__linux__)*/
228
229 static int
makeSparseFile(const off_t offsets[]G_GNUC_UNUSED,const bool startData G_GNUC_UNUSED)230 makeSparseFile(const off_t offsets[] G_GNUC_UNUSED,
231 const bool startData G_GNUC_UNUSED)
232 {
233 return -1;
234 }
235
236
237 static bool
holesSupported(void)238 holesSupported(void)
239 {
240 return false;
241 }
242
243 #endif /* !WITH_DECL_SEEK_HOLE || !defined(__linux__)*/
244
245 struct testFileInData {
246 bool startData; /* whether the list of offsets starts with data section */
247 off_t *offsets;
248 };
249
250
251 static int
testFileInData(const void * opaque)252 testFileInData(const void *opaque)
253 {
254 const struct testFileInData *data = opaque;
255 VIR_AUTOCLOSE fd = -1;
256 size_t i;
257
258 if ((fd = makeSparseFile(data->offsets, data->startData)) < 0)
259 return -1;
260
261 for (i = 0; data->offsets[i] != (off_t) -1; i++) {
262 bool shouldInData = data->startData;
263 int realInData;
264 long long shouldLen;
265 long long realLen;
266
267 if (i % 2)
268 shouldInData = !shouldInData;
269
270 if (virFileInData(fd, &realInData, &realLen) < 0)
271 return -1;
272
273 if (realInData != shouldInData) {
274 fprintf(stderr, "Unexpected data/hole. Expected %s got %s\n",
275 shouldInData ? "data" : "hole",
276 realInData ? "data" : "hole");
277 return -1;
278 }
279
280 shouldLen = data->offsets[i] * 1024;
281 if (realLen != shouldLen) {
282 fprintf(stderr, "Unexpected section length. Expected %lld got %lld\n",
283 shouldLen, realLen);
284 return -1;
285 }
286
287 if (lseek(fd, shouldLen, SEEK_CUR) < 0) {
288 fprintf(stderr, "Unable to seek\n");
289 return -1;
290 }
291 }
292
293 return 0;
294 }
295
296
297 struct testFileIsSharedFSType {
298 const char *mtabFile;
299 const char *filename;
300 const bool expected;
301 };
302
303 static int
testFileIsSharedFSType(const void * opaque G_GNUC_UNUSED)304 testFileIsSharedFSType(const void *opaque G_GNUC_UNUSED)
305 {
306 #ifndef __linux__
307 return EXIT_AM_SKIP;
308 #else
309 const struct testFileIsSharedFSType *data = opaque;
310 g_autofree char *mtabFile = NULL;
311 bool actual;
312 int ret = -1;
313
314 mtabFile = g_strdup_printf(abs_srcdir "/virfiledata/%s", data->mtabFile);
315
316 if (g_setenv("LIBVIRT_MTAB", mtabFile, TRUE) == FALSE) {
317 fprintf(stderr, "Unable to set env variable\n");
318 goto cleanup;
319 }
320
321 actual = virFileIsSharedFS(data->filename);
322
323 if (actual != data->expected) {
324 fprintf(stderr, "Unexpected FS type. Expected %d got %d\n",
325 data->expected, actual);
326 goto cleanup;
327 }
328
329 ret = 0;
330 cleanup:
331 g_unsetenv("LIBVIRT_MTAB");
332 return ret;
333 #endif
334 }
335
336
337 static int
mymain(void)338 mymain(void)
339 {
340 int ret = 0;
341 struct testFileSanitizePathData data1;
342
343 #if defined WITH_MNTENT_H && defined WITH_GETMNTENT_R
344 # define MTAB_PATH1 abs_srcdir "/virfiledata/mounts1.txt"
345 # define MTAB_PATH2 abs_srcdir "/virfiledata/mounts2.txt"
346
347 static const char *wantmounts1[] = {
348 "/proc", "/proc/sys/fs/binfmt_misc", "/proc/sys/fs/binfmt_misc",
349 };
350 static const char *wantmounts1rev[] = {
351 "/proc/sys/fs/binfmt_misc", "/proc/sys/fs/binfmt_misc", "/proc"
352 };
353 static const char *wantmounts2a[] = {
354 "/etc/aliases"
355 };
356 static const char *wantmounts2b[] = {
357 "/etc/aliases.db"
358 };
359
360 # define DO_TEST_MOUNT_SUBTREE(name, path, prefix, mounts, rev) \
361 do { \
362 struct testFileGetMountSubtreeData data = { \
363 path, prefix, mounts, G_N_ELEMENTS(mounts), rev \
364 }; \
365 if (virTestRun(name, testFileGetMountSubtree, &data) < 0) \
366 ret = -1; \
367 } while (0)
368
369 DO_TEST_MOUNT_SUBTREE("/proc normal", MTAB_PATH1, "/proc", wantmounts1, false);
370 DO_TEST_MOUNT_SUBTREE("/proc reverse", MTAB_PATH1, "/proc", wantmounts1rev, true);
371 DO_TEST_MOUNT_SUBTREE("/etc/aliases", MTAB_PATH2, "/etc/aliases", wantmounts2a, false);
372 DO_TEST_MOUNT_SUBTREE("/etc/aliases.db", MTAB_PATH2, "/etc/aliases.db", wantmounts2b, false);
373 #endif /* ! defined WITH_MNTENT_H && defined WITH_GETMNTENT_R */
374
375 #define DO_TEST_SANITIZE_PATH(PATH, EXPECT) \
376 do { \
377 data1.path = PATH; \
378 data1.expect = EXPECT; \
379 if (virTestRun(virTestCounterNext(), testFileSanitizePath, \
380 &data1) < 0) \
381 ret = -1; \
382 } while (0)
383
384 #define DO_TEST_SANITIZE_PATH_SAME(PATH) DO_TEST_SANITIZE_PATH(PATH, PATH)
385
386 virTestCounterReset("testFileSanitizePath ");
387 DO_TEST_SANITIZE_PATH("", "");
388 DO_TEST_SANITIZE_PATH("/", "/");
389 DO_TEST_SANITIZE_PATH("/path", "/path");
390 DO_TEST_SANITIZE_PATH("/path/to/blah", "/path/to/blah");
391 DO_TEST_SANITIZE_PATH("/path/", "/path");
392 DO_TEST_SANITIZE_PATH("///////", "/");
393 DO_TEST_SANITIZE_PATH("//", "//");
394 DO_TEST_SANITIZE_PATH(".", ".");
395 DO_TEST_SANITIZE_PATH("../", "..");
396 DO_TEST_SANITIZE_PATH("../../", "../..");
397 DO_TEST_SANITIZE_PATH("//foo//bar", "//foo/bar");
398 DO_TEST_SANITIZE_PATH("/bar//foo", "/bar/foo");
399 DO_TEST_SANITIZE_PATH_SAME("gluster://bar.baz/foo/hoo");
400 DO_TEST_SANITIZE_PATH_SAME("gluster://bar.baz//fooo/hoo");
401 DO_TEST_SANITIZE_PATH_SAME("gluster://bar.baz//////fooo/hoo");
402 DO_TEST_SANITIZE_PATH_SAME("gluster://bar.baz/fooo//hoo");
403 DO_TEST_SANITIZE_PATH_SAME("gluster://bar.baz/fooo///////hoo");
404
405 #define DO_TEST_IN_DATA(inData, ...) \
406 do { \
407 off_t offsets[] = {__VA_ARGS__, -1}; \
408 struct testFileInData data = { \
409 .startData = inData, .offsets = offsets, \
410 }; \
411 if (virTestRun(virTestCounterNext(), testFileInData, &data) < 0) \
412 ret = -1; \
413 } while (0)
414
415 if (holesSupported()) {
416 virTestCounterReset("testFileInData ");
417 DO_TEST_IN_DATA(true, 4, 4, 4);
418 DO_TEST_IN_DATA(false, 4, 4, 4);
419 DO_TEST_IN_DATA(true, 8, 8, 8);
420 DO_TEST_IN_DATA(false, 8, 8, 8);
421 DO_TEST_IN_DATA(true, 8, 16, 32, 64, 128, 256, 512);
422 DO_TEST_IN_DATA(false, 8, 16, 32, 64, 128, 256, 512);
423 }
424
425 #define DO_TEST_FILE_IS_SHARED_FS_TYPE(mtab, file, exp) \
426 do { \
427 struct testFileIsSharedFSType data = { \
428 .mtabFile = mtab, .filename = file, .expected = exp \
429 }; \
430 if (virTestRun(virTestCounterNext(), testFileIsSharedFSType, &data) < 0) \
431 ret = -1; \
432 } while (0)
433
434 virTestCounterReset("testFileIsSharedFSType ");
435 DO_TEST_FILE_IS_SHARED_FS_TYPE("mounts1.txt", "/boot/vmlinuz", false);
436 DO_TEST_FILE_IS_SHARED_FS_TYPE("mounts2.txt", "/run/user/501/gvfs/some/file", false);
437 DO_TEST_FILE_IS_SHARED_FS_TYPE("mounts3.txt", "/nfs/file", true);
438 DO_TEST_FILE_IS_SHARED_FS_TYPE("mounts3.txt", "/nfs/blah", false);
439 DO_TEST_FILE_IS_SHARED_FS_TYPE("mounts3.txt", "/gluster/file", true);
440 DO_TEST_FILE_IS_SHARED_FS_TYPE("mounts3.txt", "/gluster/sshfs/file", false);
441 DO_TEST_FILE_IS_SHARED_FS_TYPE("mounts3.txt", "/some/symlink/file", true);
442 DO_TEST_FILE_IS_SHARED_FS_TYPE("mounts3.txt", "/ceph/file", true);
443 DO_TEST_FILE_IS_SHARED_FS_TYPE("mounts3.txt", "/ceph/multi/file", true);
444 DO_TEST_FILE_IS_SHARED_FS_TYPE("mounts3.txt", "/gpfs/data", true);
445 DO_TEST_FILE_IS_SHARED_FS_TYPE("mounts3.txt", "/quobyte", true);
446
447 return ret != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
448 }
449
450 #ifdef __linux__
451 VIR_TEST_MAIN_PRELOAD(mymain, VIR_TEST_MOCK("virfile"))
452 #else
453 VIR_TEST_MAIN(mymain)
454 #endif
455