1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved.
4 * Copyright (c) Linux Test Project, 2014-2024
5 * Author: Stanislav Kholmanskikh <[email protected]>
6 */
7
8 #include <sys/statvfs.h>
9 #include <linux/fs.h>
10 #include <errno.h>
11 #include <linux/fiemap.h>
12 #include <stdlib.h>
13 #include <stdbool.h>
14
15 #define TST_NO_DEFAULT_MAIN
16 #define DEFAULT_MAX_SWAPFILE 32
17 #define BUFSIZE 200
18
19 #include "tst_test.h"
20 #include "libswap.h"
21 #include "lapi/syscalls.h"
22 #include "tst_kconfig.h"
23 #include "tst_kvercmp.h"
24 #include "tst_safe_stdio.h"
25
26 static const char *const swap_supported_fs[] = {
27 "btrfs",
28 "ext2",
29 "ext3",
30 "ext4",
31 "xfs",
32 "vfat",
33 "exfat",
34 "ntfs",
35 NULL
36 };
37
set_nocow_attr(const char * filename)38 static void set_nocow_attr(const char *filename)
39 {
40 int fd;
41 int attrs;
42
43 tst_res(TINFO, "FS_NOCOW_FL attribute set on %s", filename);
44
45 fd = SAFE_OPEN(filename, O_RDONLY);
46
47 SAFE_IOCTL(fd, FS_IOC_GETFLAGS, &attrs);
48
49 attrs |= FS_NOCOW_FL;
50
51 SAFE_IOCTL(fd, FS_IOC_SETFLAGS, &attrs);
52
53 SAFE_CLOSE(fd);
54 }
55
prealloc_contiguous_file(const char * path,size_t bs,size_t bcount)56 static int prealloc_contiguous_file(const char *path, size_t bs, size_t bcount)
57 {
58 int fd;
59
60 fd = open(path, O_CREAT|O_WRONLY|O_TRUNC, 0600);
61 if (fd < 0)
62 return -1;
63
64 /* Btrfs file need set 'nocow' attribute */
65 if (tst_fs_type(path) == TST_BTRFS_MAGIC)
66 set_nocow_attr(path);
67
68 if (tst_prealloc_size_fd(fd, bs, bcount)) {
69 close(fd);
70 unlink(path);
71 return -1;
72 }
73
74 if (close(fd) < 0) {
75 unlink(path);
76 return -1;
77 }
78
79 return 0;
80 }
81
file_is_contiguous(const char * filename)82 static int file_is_contiguous(const char *filename)
83 {
84 int fd, contiguous = 0;
85 struct fiemap *fiemap;
86
87 if (tst_fibmap(filename) == 0) {
88 contiguous = 1;
89 goto out;
90 }
91
92 if (tst_fs_type(filename) == TST_TMPFS_MAGIC)
93 goto out;
94
95 fd = SAFE_OPEN(filename, O_RDONLY);
96
97 fiemap = (struct fiemap *)SAFE_MALLOC(sizeof(struct fiemap)
98 + sizeof(struct fiemap_extent));
99
100 memset(fiemap, 0, sizeof(struct fiemap) + sizeof(struct fiemap_extent));
101
102 fiemap->fm_start = 0;
103 fiemap->fm_length = ~0;
104 fiemap->fm_flags = 0;
105 fiemap->fm_extent_count = 1;
106
107 SAFE_IOCTL(fd, FS_IOC_FIEMAP, fiemap);
108
109 /*
110 * fiemap->fm_mapped_extents != 1:
111 * This checks if the file does not have exactly one extent. If there are more
112 * or zero extents, the file is not stored in a single contiguous block.
113 *
114 * fiemap->fm_extents[0].fe_logical != 0:
115 * This checks if the first extent does not start at the logical offset 0 of
116 * the file. If it doesn't, it indicates that the file's first block of data
117 * is not at the beginning of the file, which implies non-contiguity.
118 *
119 * (fiemap->fm_extents[0].fe_flags & FIEMAP_EXTENT_LAST) != FIEMAP_EXTENT_LAST:
120 * This checks if the first extent does not have the FIEMAP_EXTENT_LAST flag set.
121 * If the flag isn't set, it means that this extent is not the last one, suggesting
122 * that there are more extents and the file is not contiguous.
123 */
124 if (fiemap->fm_mapped_extents != 1 ||
125 fiemap->fm_extents[0].fe_logical != 0 ||
126 (fiemap->fm_extents[0].fe_flags & FIEMAP_EXTENT_LAST) != FIEMAP_EXTENT_LAST) {
127
128 tst_res(TINFO, "File '%s' is not contiguous", filename);
129 contiguous = 0;
130 }
131
132 SAFE_CLOSE(fd);
133 free(fiemap);
134
135 out:
136 return contiguous;
137 }
138
make_swapfile(const char * file,const int lineno,const char * swapfile,unsigned int num,int safe,enum swapfile_method method)139 int make_swapfile(const char *file, const int lineno,
140 const char *swapfile, unsigned int num,
141 int safe, enum swapfile_method method)
142 {
143 struct statvfs fs_info;
144 unsigned long blk_size;
145 unsigned int blocks = 0;
146 size_t pg_size = sysconf(_SC_PAGESIZE);
147 char mnt_path[PATH_MAX];
148
149 if (statvfs(".", &fs_info) == -1)
150 tst_brk_(file, lineno, TBROK, "statvfs failed");
151
152 blk_size = fs_info.f_bsize;
153
154 if (method == SWAPFILE_BY_SIZE) {
155 tst_res_(file, lineno, TINFO, "create a swapfile size of %u megabytes (MB)", num);
156 blocks = num * 1024 * 1024 / blk_size;
157 } else if (method == SWAPFILE_BY_BLKS) {
158 blocks = num;
159 tst_res_(file, lineno, TINFO, "create a swapfile with %u block numbers", blocks);
160 } else {
161 tst_brk_(file, lineno, TBROK, "Invalid method, please see include/libswap.h");
162 }
163
164 /* To guarantee at least one page can be swapped out */
165 if (blk_size * blocks < pg_size) {
166 tst_res_(file, lineno, TWARN, "Swapfile size is less than the system page size. "
167 "Using page size (%lu bytes) instead of block size (%lu bytes).",
168 (unsigned long)pg_size, blk_size);
169 blk_size = pg_size;
170 }
171
172 if (sscanf(swapfile, "%[^/]", mnt_path) != 1)
173 tst_brk_(file, lineno, TBROK, "sscanf failed");
174
175 if (!tst_fs_has_free(mnt_path, blk_size * blocks, TST_BYTES))
176 tst_brk_(file, lineno, TCONF, "Insufficient disk space to create swap file");
177
178 /* create file */
179 if (prealloc_contiguous_file(swapfile, blk_size, blocks) != 0)
180 tst_brk_(file, lineno, TBROK, "Failed to create swapfile");
181
182 /* Fill the file if needed (specific to old xfs filesystems) */
183 if (tst_fs_type(swapfile) == TST_XFS_MAGIC) {
184 if (tst_fill_file(swapfile, 0, blk_size, blocks) != 0)
185 tst_brk_(file, lineno, TBROK, "Failed to fill swapfile");
186 }
187
188 /* make the file swapfile */
189 const char *const argv[] = {"mkswap", swapfile, NULL};
190
191 return tst_cmd(argv, "/dev/null", "/dev/null", safe ?
192 TST_CMD_PASS_RETVAL | TST_CMD_TCONF_ON_MISSING : 0);
193 }
194
is_swap_supported(const char * filename)195 bool is_swap_supported(const char *filename)
196 {
197 int i, sw_support = 0;
198 int ret = SAFE_MAKE_SMALL_SWAPFILE(filename);
199 int fi_contiguous = file_is_contiguous(filename);
200 long fs_type = tst_fs_type(filename);
201 const char *fstype = tst_fs_type_name(fs_type);
202
203 if (fs_type == TST_BTRFS_MAGIC &&
204 tst_kvercmp(5, 0, 0) < 0)
205 tst_brk(TCONF, "Swapfile on Btrfs (kernel < 5.0) not implemented");
206
207 for (i = 0; swap_supported_fs[i]; i++) {
208 if (strstr(fstype, swap_supported_fs[i])) {
209 sw_support = 1;
210 break;
211 }
212 }
213
214 if (ret != 0) {
215 if (fi_contiguous == 0 && sw_support == 0) {
216 tst_brk(TCONF, "mkswap on %s not supported", fstype);
217 } else {
218 tst_res(TFAIL, "mkswap on %s failed", fstype);
219 return false;
220 }
221 }
222
223 TEST(tst_syscall(__NR_swapon, filename, 0));
224 if (TST_RET == -1) {
225 if (errno == EPERM) {
226 tst_brk(TCONF, "Permission denied for swapon()");
227 } else if (errno == EINVAL && fi_contiguous == 0 && sw_support == 0) {
228 tst_brk(TCONF, "Swapfile on %s not implemented", fstype);
229 } else {
230 tst_res(TFAIL | TTERRNO, "swapon() on %s failed", fstype);
231 return false;
232 }
233 }
234
235 TEST(tst_syscall(__NR_swapoff, filename, 0));
236 if (TST_RET == -1) {
237 tst_res(TFAIL | TTERRNO, "swapoff on %s failed", fstype);
238 return false;
239 }
240
241 return true;
242 }
243
tst_max_swapfiles(void)244 int tst_max_swapfiles(void)
245 {
246 unsigned int swp_migration_num = 0, swp_hwpoison_num = 0,
247 swp_device_num = 0, swp_pte_marker_num = 0,
248 swp_swapin_error_num = 0;
249 struct tst_kconfig_var migration = TST_KCONFIG_INIT("CONFIG_MIGRATION");
250 struct tst_kconfig_var memory = TST_KCONFIG_INIT("CONFIG_MEMORY_FAILURE");
251 struct tst_kconfig_var device = TST_KCONFIG_INIT("CONFIG_DEVICE_PRIVATE");
252 struct tst_kconfig_var marker = TST_KCONFIG_INIT("CONFIG_PTE_MARKER");
253 struct tst_kern_exv kvers_marker_migration[] = {
254 /* RHEL9 kernel has patch 6c287605f and 679d10331 since 5.14.0-179 */
255 { "RHEL9", "5.14.0-179" },
256 { NULL, NULL},
257 };
258
259 struct tst_kern_exv kvers_device[] = {
260 /* SLES12-SP4 has patch 5042db43cc26 since 4.12.14-5.5 */
261 { "SLES", "4.12.14-5.5" },
262 { NULL, NULL},
263 };
264
265 tst_kconfig_read(&migration, 1);
266 tst_kconfig_read(&memory, 1);
267 tst_kconfig_read(&device, 1);
268 tst_kconfig_read(&marker, 1);
269
270 if (migration.choice == 'y') {
271 if (tst_kvercmp2(5, 19, 0, kvers_marker_migration) < 0)
272 swp_migration_num = 2;
273 else
274 swp_migration_num = 3;
275 }
276
277 if (memory.choice == 'y')
278 swp_hwpoison_num = 1;
279
280 if (device.choice == 'y') {
281 if (tst_kvercmp2(4, 14, 0, kvers_device) >= 0)
282 swp_device_num = 2;
283 if (tst_kvercmp(5, 14, 0) >= 0)
284 swp_device_num = 4;
285 }
286
287 if ((marker.choice == 'y' &&
288 tst_kvercmp2(5, 19, 0, kvers_marker_migration) >= 0)
289 || tst_kvercmp(6, 2, 0) >= 0) {
290 swp_pte_marker_num = 1;
291 }
292
293 if ((tst_kvercmp(5, 19, 0) >= 0) && (tst_kvercmp(6, 2, 0) < 0))
294 swp_swapin_error_num = 1;
295
296 return DEFAULT_MAX_SWAPFILE - swp_migration_num - swp_hwpoison_num
297 - swp_device_num - swp_pte_marker_num - swp_swapin_error_num;
298 }
299
tst_count_swaps(void)300 int tst_count_swaps(void)
301 {
302 FILE *fp;
303 int used = -1;
304 char buf[BUFSIZE];
305
306 fp = SAFE_FOPEN("/proc/swaps", "r");
307 if (fp == NULL)
308 return -1;
309
310 while (fgets(buf, BUFSIZE, fp) != NULL)
311 used++;
312
313 SAFE_FCLOSE(fp);
314 if (used < 0)
315 tst_brk(TBROK, "can't read /proc/swaps to get used swapfiles resource total");
316
317 return used;
318 }
319