blob: 63a995f4e31eaf673ce166828000bc50214c7486 [file] [edit]
// 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 <stanislav.kholmanskikh@oracle.com>
*/
#include <sys/statvfs.h>
#include <linux/fs.h>
#include <errno.h>
#include <linux/fiemap.h>
#include <stdlib.h>
#include <stdbool.h>
#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;
}