* Copyright (C) 2022-2025 Colin Ian King.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "stress-ng.h"
#include "core-killpid.h"
#include "core-lock.h"
#define TOUCH_PROCS (4)
#define TOUCH_RANDOM (0)
#define TOUCH_OPEN (1)
#define TOUCH_CREAT (2)
typedef struct {
const char *opt;
const int open_flag;
} touch_opts_t;
typedef struct {
const char *method;
const int method_type;
} touch_method_t;
#if defined(O_DIRECT)
#define TOUCH_OPT_DIRECT O_DIRECT
#else
#define TOUCH_OPT_DIRECT (0)
#endif
#if defined(O_DSYNC)
#define TOUCH_OPT_DSYNC O_DSYNC
#else
#define TOUCH_OPT_DSYNC (0)
#endif
#if defined(O_EXCL)
#define TOUCH_OPT_EXCL O_EXCL
#else
#define TOUCH_OPT_EXCL (0)
#endif
#if defined(O_NOATIME)
#define TOUCH_OPT_NOATIME O_NOATIME
#else
#define TOUCH_OPT_NOATIME (0)
#endif
#if defined(O_SYNC)
#define TOUCH_OPT_SYNC O_SYNC
#else
#define TOUCH_OPT_SYNC (0)
#endif
#if defined(O_TRUNC)
#define TOUCH_OPT_TRUNC O_TRUNC
#else
#define TOUCH_OPT_TRUNC (0)
#endif
static void *touch_lock;
#define TOUCH_OPT_ALL \
(TOUCH_OPT_DIRECT | \
TOUCH_OPT_DSYNC | \
TOUCH_OPT_EXCL | \
TOUCH_OPT_NOATIME | \
TOUCH_OPT_SYNC | \
TOUCH_OPT_TRUNC )
static const stress_help_t help[] = {
{ NULL, "touch N", "start N stressors that touch and remove files" },
{ NULL, "touch-method M", "specify method to touch tile file, open | create" },
{ NULL, "touch-ops N", "stop after N touch bogo operations" },
{ NULL, "touch-opts list", "touch open options all, direct, dsync, excl, noatime, sync, trunc" },
{ NULL, NULL, NULL }
};
static const touch_opts_t touch_opts[] = {
{ "all", TOUCH_OPT_ALL },
{ "direct", TOUCH_OPT_DIRECT },
{ "dsync", TOUCH_OPT_DSYNC },
{ "excl", TOUCH_OPT_EXCL },
{ "noatime", TOUCH_OPT_NOATIME },
{ "sync", TOUCH_OPT_SYNC },
{ "trunc", TOUCH_OPT_TRUNC },
};
static const touch_method_t touch_methods[] = {
{ "random", TOUCH_RANDOM },
{ "open", TOUCH_OPEN },
{ "creat", TOUCH_CREAT },
};
* stress_touch_opts
* parse --touch-opts option(s) list
*/
static void stress_touch_opts(const char *opt_name, const char *opt_arg, stress_type_id_t *type_id, void *value)
{
char *str, *ptr;
const char *token;
int open_flags = 0;
str = stress_const_optdup(opt_arg);
if (!str) {
(void)fprintf(stderr, "%s option: cannot dup string '%s'\n",
opt_name, opt_arg);
longjmp(g_error_env, 1);
}
for (ptr = str; (token = strtok(ptr, ",")) != NULL; ptr = NULL) {
size_t i;
bool opt_ok = false;
for (i = 0; i < SIZEOF_ARRAY(touch_opts); i++) {
if (!strcmp(token, touch_opts[i].opt)) {
open_flags |= touch_opts[i].open_flag;
opt_ok = true;
}
}
if (!opt_ok) {
(void)fprintf(stderr, "%s option '%s' not known, options are:", opt_name, token);
for (i = 0; i < SIZEOF_ARRAY(touch_opts); i++)
(void)fprintf(stderr, " %s", touch_opts[i].opt);
(void)fprintf(stderr, "\n");
free(str);
longjmp(g_error_env, 1);
}
}
*type_id = TYPE_ID_INT;
*(int *)value = open_flags;
free(str);
}
static const char *stress_touch_method(const size_t i)
{
return (i < SIZEOF_ARRAY(touch_methods)) ? touch_methods[i].method : NULL;
}
static const stress_opt_t opts[] = {
{ OPT_touch_opts, "touch-opts", TYPE_ID_CALLBACK, 0, 0, stress_touch_opts },
{ OPT_touch_method, "touch-method", TYPE_ID_SIZE_T_METHOD, 0, 0, stress_touch_method },
END_OPT,
};
static void stress_touch_dir_clean(stress_args_t *args)
{
char tmp[PATH_MAX];
DIR *dir;
const struct dirent *d;
sync();
stress_temp_dir(tmp, sizeof(tmp), args->name,
args->pid, args->instance);
dir = opendir(tmp);
if (!dir)
return;
while ((d = readdir(dir)) != NULL) {
* One file name (with a path) and one NUL character (PATH_MAX),
* one slash (1), another file name without a path (NAME_MAX).
* This can produce a result that exceeds the OS limit, but the
* buffer size will be sufficient to join the strings safely
* without upsetting the compiler.
*/
char filename[PATH_MAX + 1 + NAME_MAX];
struct stat statbuf;
(void)snprintf(filename, sizeof(filename), "%s/%s", tmp, d->d_name);
if (shim_stat(filename, &statbuf) < 0)
continue;
if ((statbuf.st_mode & S_IFMT) == S_IFREG)
(void)shim_unlink(filename);
}
(void)closedir(dir);
}
static void stress_touch_loop(
stress_args_t *args,
const int touch_method_type,
const int open_flags)
{
do {
char filename[PATH_MAX];
uint64_t counter;
int fd, ret;
ret = stress_lock_acquire(touch_lock);
if (UNLIKELY(ret))
break;
counter = stress_bogo_get(args);
stress_bogo_inc(args);
ret = stress_lock_release(touch_lock);
if (UNLIKELY(ret))
break;
(void)stress_temp_filename_args(args, filename,
sizeof(filename), counter);
switch (touch_method_type) {
default:
case TOUCH_RANDOM:
if (stress_mwc1())
fd = creat(filename, S_IRUSR | S_IWUSR);
else
fd = open(filename, O_CREAT | O_WRONLY | open_flags, S_IRUSR | S_IWUSR);
break;
case TOUCH_OPEN:
fd = open(filename, O_CREAT | O_WRONLY | open_flags, S_IRUSR | S_IWUSR);
break;
case TOUCH_CREAT:
fd = creat(filename, S_IRUSR | S_IWUSR);
break;
}
if (UNLIKELY(fd < 0)) {
switch (errno) {
#if defined(EEXIST)
case EEXIST:
#endif
#if defined(EFAULT)
case EFAULT:
#endif
#if defined(EFBIG)
case EFBIG:
#endif
#if defined(EINVAL)
case EINVAL:
#endif
#if defined(EISDIR)
case EISDIR:
#endif
#if defined(EMFILE)
case EMFILE:
#endif
#if defined(ENOENT)
case ENOENT:
#endif
#if defined(ENOTDIR)
case ENOTDIR:
#endif
#if defined(ENXIO)
case ENXIO:
#endif
#if defined(EOPNOTSUPP)
case EOPNOTSUPP:
#endif
#if defined(ETXTBSY)
case ETXTBSY:
#endif
#if defined(EWOULDBLOCK)
case EWOULDBLOCK:
#endif
#if defined(EBADF)
case EBADF:
#endif
case -1:
pr_fail("%s: creat %s failed, errno=%d (%s)\n",
args->name, filename, errno, strerror(errno));
break;
default:
break;
}
} else {
(void)close(fd);
}
(void)shim_unlink(filename);
} while (stress_continue(args));
}
* stress_touch
* stress file creation and removal
*/
static int stress_touch(stress_args_t *args)
{
int ret;
int open_flags = 0;
size_t touch_method = 0;
int touch_method_type;
stress_pid_t *s_pids, *s_pids_head = NULL;
size_t i;
s_pids = stress_sync_s_pids_mmap(TOUCH_PROCS);
if (s_pids == MAP_FAILED) {
pr_inf_skip("%s: failed to mmap %d PIDs%s, skipping stressor\n",
args->name, TOUCH_PROCS, stress_get_memfree_str());
return EXIT_NO_RESOURCE;
}
touch_lock = stress_lock_create("counter");
if (!touch_lock) {
pr_inf_skip("%s: cannot create lock, skipping stressor\n", args->name);
(void)stress_sync_s_pids_munmap(s_pids, TOUCH_PROCS);
return EXIT_NO_RESOURCE;
}
(void)stress_get_setting("touch-opts", &open_flags);
(void)stress_get_setting("touch-method", &touch_method);
touch_method_type = touch_methods[touch_method].method_type;
if (stress_instance_zero(args) &&
(touch_method_type == TOUCH_CREAT) &&
(open_flags != 0))
pr_inf("%s: note: touch-opts are not used for creat touch method\n", args->name);
ret = stress_temp_dir_mk_args(args);
if (ret < 0) {
(void)stress_lock_destroy(touch_lock);
(void)stress_sync_s_pids_munmap(s_pids, TOUCH_PROCS);
return stress_exit_status(-ret);
}
for (i = 0; i < TOUCH_PROCS; i++) {
stress_sync_start_init(&s_pids[i]);
s_pids[i].pid = fork();
if (s_pids[i].pid == 0) {
s_pids[i].pid = getpid();
stress_sync_start_wait_s_pid(&s_pids[i]);
stress_touch_loop(args, touch_method_type, open_flags);
_exit(0);
} else if (s_pids[i].pid > 0) {
stress_sync_start_s_pid_list_add(&s_pids_head, &s_pids[i]);
}
}
stress_set_proc_state(args->name, STRESS_STATE_SYNC_WAIT);
stress_sync_start_wait(args);
stress_sync_start_cont_list(s_pids_head);
stress_set_proc_state(args->name, STRESS_STATE_RUN);
stress_touch_loop(args, touch_method_type, open_flags);
stress_continue_set_flag(false);
stress_set_proc_state(args->name, STRESS_STATE_DEINIT);
stress_kill_and_wait_many(args, s_pids, TOUCH_PROCS, SIGALRM, true);
stress_touch_dir_clean(args);
(void)stress_temp_dir_rm_args(args);
(void)stress_lock_destroy(touch_lock);
(void)stress_sync_s_pids_munmap(s_pids, TOUCH_PROCS);
return EXIT_SUCCESS;
}
const stressor_info_t stress_touch_info = {
.stressor = stress_touch,
.classifier = CLASS_FILESYSTEM | CLASS_OS,
.opts = opts,
.verify = VERIFY_ALWAYS,
.help = help
};