// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved. * Copyright (c) Linux Test Project, 2014-2024 * Author: Stanislav Kholmanskikh */ #include #include #include #include #include #include #define TST_NO_DEFAULT_MAIN #define DEFAULT_MAX_SWAPFILE 32 #define BUFSIZE 200 #include "tst_test.h" #include "libswap.h" #include "lapi/syscalls.h" #include "tst_kconfig.h" #include "tst_kvercmp.h" #include "tst_safe_stdio.h" static const char *const swap_supported_fs[] = { "btrfs", "ext2", "ext3", "ext4", "xfs", "vfat", "exfat", "ntfs", NULL }; static void set_nocow_attr(const char *filename) { int fd; int attrs; tst_res(TINFO, "FS_NOCOW_FL attribute set on %s", filename); fd = SAFE_OPEN(filename, O_RDONLY); SAFE_IOCTL(fd, FS_IOC_GETFLAGS, &attrs); attrs |= FS_NOCOW_FL; SAFE_IOCTL(fd, FS_IOC_SETFLAGS, &attrs); SAFE_CLOSE(fd); } static int prealloc_contiguous_file(const char *path, size_t bs, size_t bcount) { int fd; fd = open(path, O_CREAT|O_WRONLY|O_TRUNC, 0600); if (fd < 0) return -1; /* Btrfs file need set 'nocow' attribute */ if (tst_fs_type(path) == TST_BTRFS_MAGIC) set_nocow_attr(path); if (tst_prealloc_size_fd(fd, bs, bcount)) { close(fd); unlink(path); return -1; } if (close(fd) < 0) { unlink(path); return -1; } return 0; } static int file_is_contiguous(const char *filename) { int fd, contiguous = 0; struct fiemap *fiemap; if (tst_fibmap(filename) == 0) { contiguous = 1; goto out; } if (tst_fs_type(filename) == TST_TMPFS_MAGIC) goto out; fd = SAFE_OPEN(filename, O_RDONLY); fiemap = (struct fiemap *)SAFE_MALLOC(sizeof(struct fiemap) + sizeof(struct fiemap_extent)); memset(fiemap, 0, sizeof(struct fiemap) + sizeof(struct fiemap_extent)); fiemap->fm_start = 0; fiemap->fm_length = ~0; fiemap->fm_flags = 0; fiemap->fm_extent_count = 1; SAFE_IOCTL(fd, FS_IOC_FIEMAP, fiemap); /* * fiemap->fm_mapped_extents != 1: * This checks if the file does not have exactly one extent. If there are more * or zero extents, the file is not stored in a single contiguous block. * * fiemap->fm_extents[0].fe_logical != 0: * This checks if the first extent does not start at the logical offset 0 of * the file. If it doesn't, it indicates that the file's first block of data * is not at the beginning of the file, which implies non-contiguity. * * (fiemap->fm_extents[0].fe_flags & FIEMAP_EXTENT_LAST) != FIEMAP_EXTENT_LAST: * This checks if the first extent does not have the FIEMAP_EXTENT_LAST flag set. * If the flag isn't set, it means that this extent is not the last one, suggesting * that there are more extents and the file is not contiguous. */ if (fiemap->fm_mapped_extents != 1 || fiemap->fm_extents[0].fe_logical != 0 || (fiemap->fm_extents[0].fe_flags & FIEMAP_EXTENT_LAST) != FIEMAP_EXTENT_LAST) { tst_res(TINFO, "File '%s' is not contiguous", filename); contiguous = 0; } SAFE_CLOSE(fd); free(fiemap); out: return contiguous; } int make_swapfile(const char *file, const int lineno, const char *swapfile, unsigned int num, int safe, enum swapfile_method method) { struct statvfs fs_info; unsigned long blk_size; unsigned int blocks = 0; size_t pg_size = sysconf(_SC_PAGESIZE); char mnt_path[PATH_MAX]; if (statvfs(".", &fs_info) == -1) tst_brk_(file, lineno, TBROK, "statvfs failed"); blk_size = fs_info.f_bsize; if (method == SWAPFILE_BY_SIZE) { tst_res_(file, lineno, TINFO, "create a swapfile size of %u megabytes (MB)", num); blocks = num * 1024 * 1024 / blk_size; } else if (method == SWAPFILE_BY_BLKS) { blocks = num; tst_res_(file, lineno, TINFO, "create a swapfile with %u block numbers", blocks); } else { tst_brk_(file, lineno, TBROK, "Invalid method, please see include/libswap.h"); } /* To guarantee at least one page can be swapped out */ if (blk_size * blocks < pg_size) { tst_res_(file, lineno, TWARN, "Swapfile size is less than the system page size. " "Using page size (%lu bytes) instead of block size (%lu bytes).", (unsigned long)pg_size, blk_size); blk_size = pg_size; } if (sscanf(swapfile, "%[^/]", mnt_path) != 1) tst_brk_(file, lineno, TBROK, "sscanf failed"); if (!tst_fs_has_free(mnt_path, blk_size * blocks, TST_BYTES)) tst_brk_(file, lineno, TCONF, "Insufficient disk space to create swap file"); /* create file */ if (prealloc_contiguous_file(swapfile, blk_size, blocks) != 0) tst_brk_(file, lineno, TBROK, "Failed to create swapfile"); /* Fill the file if needed (specific to old xfs filesystems) */ if (tst_fs_type(swapfile) == TST_XFS_MAGIC) { if (tst_fill_file(swapfile, 0, blk_size, blocks) != 0) tst_brk_(file, lineno, TBROK, "Failed to fill swapfile"); } /* make the file swapfile */ const char *const argv[] = {"mkswap", swapfile, NULL}; return tst_cmd(argv, "/dev/null", "/dev/null", safe ? TST_CMD_PASS_RETVAL | TST_CMD_TCONF_ON_MISSING : 0); } bool is_swap_supported(const char *filename) { int i, sw_support = 0; int ret = SAFE_MAKE_SMALL_SWAPFILE(filename); int fi_contiguous = file_is_contiguous(filename); long fs_type = tst_fs_type(filename); const char *fstype = tst_fs_type_name(fs_type); if (fs_type == TST_BTRFS_MAGIC && tst_kvercmp(5, 0, 0) < 0) tst_brk(TCONF, "Swapfile on Btrfs (kernel < 5.0) not implemented"); for (i = 0; swap_supported_fs[i]; i++) { if (strstr(fstype, swap_supported_fs[i])) { sw_support = 1; break; } } if (ret != 0) { if (fi_contiguous == 0 && sw_support == 0) { tst_brk(TCONF, "mkswap on %s not supported", fstype); } else { tst_res(TFAIL, "mkswap on %s failed", fstype); return false; } } TEST(tst_syscall(__NR_swapon, filename, 0)); if (TST_RET == -1) { if (errno == EPERM) { tst_brk(TCONF, "Permission denied for swapon()"); } else if (errno == EINVAL && fi_contiguous == 0 && sw_support == 0) { tst_brk(TCONF, "Swapfile on %s not implemented", fstype); } else { tst_res(TFAIL | TTERRNO, "swapon() on %s failed", fstype); return false; } } TEST(tst_syscall(__NR_swapoff, filename, 0)); if (TST_RET == -1) { tst_res(TFAIL | TTERRNO, "swapoff on %s failed", fstype); return false; } return true; } int tst_max_swapfiles(void) { unsigned int swp_migration_num = 0, swp_hwpoison_num = 0, swp_device_num = 0, swp_pte_marker_num = 0, swp_swapin_error_num = 0; struct tst_kconfig_var migration = TST_KCONFIG_INIT("CONFIG_MIGRATION"); struct tst_kconfig_var memory = TST_KCONFIG_INIT("CONFIG_MEMORY_FAILURE"); struct tst_kconfig_var device = TST_KCONFIG_INIT("CONFIG_DEVICE_PRIVATE"); struct tst_kconfig_var marker = TST_KCONFIG_INIT("CONFIG_PTE_MARKER"); struct tst_kern_exv kvers_marker_migration[] = { /* RHEL9 kernel has patch 6c287605f and 679d10331 since 5.14.0-179 */ { "RHEL9", "5.14.0-179" }, { NULL, NULL}, }; struct tst_kern_exv kvers_marker_migration2[] = { /* RHEL9 kernel has patch ca92ea3dc5a since 5.14.0-441 */ { "RHEL9", "5.14.0-441" }, { NULL, NULL}, }; struct tst_kern_exv kvers_device[] = { /* SLES12-SP4 has patch 5042db43cc26 since 4.12.14-5.5 */ { "SLES", "4.12.14-5.5" }, { NULL, NULL}, }; tst_kconfig_read(&migration, 1); tst_kconfig_read(&memory, 1); tst_kconfig_read(&device, 1); tst_kconfig_read(&marker, 1); if (migration.choice == 'y') { if (tst_kvercmp2(5, 19, 0, kvers_marker_migration) < 0) swp_migration_num = 2; else swp_migration_num = 3; } if (memory.choice == 'y') swp_hwpoison_num = 1; if (device.choice == 'y') { if (tst_kvercmp2(4, 14, 0, kvers_device) >= 0) swp_device_num = 2; if (tst_kvercmp(5, 14, 0) >= 0) swp_device_num = 4; } if ((marker.choice == 'y' && tst_kvercmp2(5, 19, 0, kvers_marker_migration) >= 0) || tst_kvercmp2(6, 2, 0, kvers_marker_migration2) >= 0) { swp_pte_marker_num = 1; } if ((tst_kvercmp(5, 19, 0) >= 0) && (tst_kvercmp(6, 2, 0) < 0)) swp_swapin_error_num = 1; return DEFAULT_MAX_SWAPFILE - swp_migration_num - swp_hwpoison_num - swp_device_num - swp_pte_marker_num - swp_swapin_error_num; } int tst_count_swaps(void) { FILE *fp; int used = -1; char buf[BUFSIZE]; fp = SAFE_FOPEN("/proc/swaps", "r"); if (fp == NULL) return -1; while (fgets(buf, BUFSIZE, fp) != NULL) used++; SAFE_FCLOSE(fp); if (used < 0) tst_brk(TBROK, "can't read /proc/swaps to get used swapfiles resource total"); return used; }