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