xref: /aosp_15_r20/external/ltp/libs/libltpswap/libswap.c (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
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