1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2017 Cyril Hrubis <chrubis@suse.cz>
4 * Copyright (c) 2019 SUSE LLC <mdoucha@suse.cz>
5 */
6
7 /*
8 * Tests that writing to fallocated file works when filesystem is full.
9 * Test scenario:
10 * - fallocate() some empty blocks
11 * - fill the filesystem
12 * - write() into the preallocated space
13 * - try to fallocate() more blocks until we get ENOSPC
14 * - write() into the extra allocated space
15 * - deallocate part of the file
16 * - write() to the end of file to check that some blocks were freed
17 */
18
19 #define _GNU_SOURCE
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <fcntl.h>
25 #include "tst_test.h"
26 #include "lapi/fallocate.h"
27
28 #define MNTPOINT "mntpoint"
29 #define FALLOCATE_BLOCKS 256
30 #define DEALLOCATE_BLOCKS 64
31 #define TESTED_FLAGS "fallocate(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE)"
32
33 static char *buf;
34 static blksize_t blocksize;
35 static long bufsize;
36
setup(void)37 static void setup(void)
38 {
39 int fd;
40 struct stat statbuf;
41
42 fd = SAFE_OPEN(MNTPOINT "/test_file", O_WRONLY | O_CREAT, 0644);
43
44 /*
45 * Use real FS block size, otherwise fallocate() call will test
46 * different things on different platforms
47 */
48 SAFE_FSTAT(fd, &statbuf);
49 blocksize = statbuf.st_blksize;
50 bufsize = FALLOCATE_BLOCKS * blocksize;
51 buf = SAFE_MALLOC(bufsize);
52 SAFE_CLOSE(fd);
53 }
54
run(void)55 static void run(void)
56 {
57 int fd;
58 long extsize, tmp;
59
60 fd = SAFE_OPEN(MNTPOINT "/test_file", O_WRONLY | O_CREAT | O_TRUNC,
61 0644);
62 TEST(fallocate(fd, 0, 0, bufsize));
63
64 if (TST_RET) {
65 if (TST_ERR == ENOTSUP)
66 tst_brk(TCONF | TTERRNO, "fallocate() not supported");
67
68 tst_brk(TBROK | TTERRNO, "fallocate(fd, 0, 0, %ld)", bufsize);
69 }
70
71 tst_fill_fs(MNTPOINT, 1);
72
73 TEST(write(fd, buf, bufsize));
74
75 if (TST_RET < 0)
76 tst_res(TFAIL | TTERRNO, "write() failed unexpectedly");
77 else if (TST_RET != bufsize)
78 tst_res(TFAIL, "Short write(): %ld bytes (expected %zu)",
79 TST_RET, bufsize);
80 else
81 tst_res(TPASS, "write() wrote %ld bytes", TST_RET);
82
83 /*
84 * Some file systems may still have a few extra blocks that can be
85 * allocated.
86 */
87 for (TST_RET = 0, extsize = 0; !TST_RET; extsize += blocksize)
88 TEST(fallocate(fd, 0, bufsize + extsize, blocksize));
89
90 if (TST_RET != -1) {
91 tst_res(TFAIL, "Invalid fallocate() return value %ld", TST_RET);
92 return;
93 }
94
95 if (TST_ERR != ENOSPC) {
96 tst_res(TFAIL | TTERRNO, "fallocate() should fail with ENOSPC");
97 return;
98 }
99
100 /* The loop above always counts 1 more block than it should. */
101 extsize -= blocksize;
102 tst_res(TINFO, "fallocate()d %ld extra blocks on full FS",
103 extsize / blocksize);
104
105 for (tmp = extsize; tmp > 0; tmp -= TST_RET) {
106 TEST(write(fd, buf, MIN(bufsize, tmp)));
107
108 if (TST_RET <= 0) {
109 tst_res(TFAIL | TTERRNO, "write() failed unexpectedly");
110 return;
111 }
112 }
113
114 tst_res(TPASS, "fallocate() on full FS");
115
116 /* Btrfs deallocates only complete extents, not individual blocks */
117 if (!strcmp(tst_device->fs_type, "btrfs"))
118 tmp = bufsize + extsize;
119 else
120 tmp = DEALLOCATE_BLOCKS * blocksize;
121
122 TEST(fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, tmp));
123
124 if (TST_RET == -1) {
125 if (TST_ERR == ENOTSUP)
126 tst_brk(TCONF, TESTED_FLAGS);
127
128 tst_brk(TBROK | TTERRNO, TESTED_FLAGS);
129 }
130 tst_res(TPASS, TESTED_FLAGS);
131
132 TEST(write(fd, buf, 10));
133 if (TST_RET == -1)
134 tst_res(TFAIL | TTERRNO, "write()");
135 else
136 tst_res(TPASS, "write()");
137
138 SAFE_CLOSE(fd);
139 tst_purge_dir(MNTPOINT);
140 }
141
cleanup(void)142 static void cleanup(void)
143 {
144 free(buf);
145 }
146
147 static struct tst_test test = {
148 .needs_root = 1,
149 .mount_device = 1,
150 .mntpoint = MNTPOINT,
151 .all_filesystems = 1,
152 .setup = setup,
153 .cleanup = cleanup,
154 .test_all = run,
155 };
156