Added performance tests and creation permission flags

This commit is contained in:
fedir 2025-05-21 16:21:33 +02:00
parent 448c862731
commit 467087d76e
Signed by: fedir
GPG Key ID: C959EE85F0C9362C
10 changed files with 175 additions and 82 deletions

View File

@ -13,6 +13,7 @@
#include "process_info.h"
#include "real_filename.h"
#include "set_mode_t.h"
#include <assert.h>
#include <stddef.h>
#define FUSE_USE_VERSION 31
@ -45,6 +46,10 @@
#include "temp_permissions_table.h"
#include "ui-socket.h"
int auto_create_perm = GRANT_PERM;
void set_auto_create_perm(int val) { auto_create_perm = val; }
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
(void)conn;
cfg->use_ino = 1;
@ -85,7 +90,7 @@ static int xmp_getattr(const char *path, struct stat *stbuf,
else
res = source_stat(path, stbuf);
if (res == -1) {
perror("Stat failed");
perror("[ICFS] Stat failed");
return -errno;
}
@ -149,7 +154,7 @@ static int xmp_opendir(const char *path, struct fuse_file_info *fi) {
d->dp = source_opendir(path);
if (d->dp == NULL) {
perror("Opendir failed");
perror("[ICFS] Opendir failed");
res = -errno;
free(d);
return res;
@ -461,7 +466,7 @@ static int xmp_create(const char *path, mode_t mode,
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(path, pi, GRANT_PERM)) {
if (!interactive_access(path, pi, auto_create_perm)) {
free(pi.name);
return -EACCES;
}

View File

@ -14,5 +14,6 @@
#include <fuse3/fuse.h>
const struct fuse_operations *get_fuse_operations();
void set_auto_create_perm(int val);
#endif

View File

@ -10,6 +10,7 @@
See the file LICENSE.
*/
#include <string.h>
#define FUSE_USE_VERSION 31
#define _GNU_SOURCE
@ -29,7 +30,26 @@ const char *mountpoint = NULL;
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: icfs <FUSE arguments> [target directory] [path to "
"the permanent permissions database\n");
"the permanent permissions database] <ICFS "
"arguments>\n\t--no-perm-on-create - do not give permanent "
"permissions to files a process creates automatically "
"(incompatible with --temp-on-create)\n\t--temp-on-create "
"- give temporary permissions to files a process creates "
"automatically (incompatible with --no-perm-on-create)\n");
return EXIT_FAILURE;
}
if ((0 == strcmp(argv[argc - 1], "--no-perm-on-create") &&
0 == strcmp(argv[argc - 2], "--temp-on-create")) ||
(0 == strcmp(argv[argc - 2], "--no-perm-on-create") &&
0 == strcmp(argv[argc - 1], "--temp-on-create"))) {
fprintf(stderr, "Usage: icfs <FUSE arguments> [target directory] [path to "
"the permanent permissions database] <ICFS "
"arguments>\n\t--no-perm-on-create - do not give permanent "
"permissions to files a process creates automatically "
"(incompatible with --temp-on-create)\n\t--temp-on-create "
"- give temporary permissions to files a process creates "
"automatically (incompatible with --no-perm-on-create)\n");
return EXIT_FAILURE;
}
@ -37,11 +57,20 @@ int main(int argc, char *argv[]) {
// permissions than it's caller reqested
umask(0);
if (0 == strcmp(argv[argc - 1], "--no-perm-on-create")) {
set_auto_create_perm(0);
argc--;
}
if (0 == strcmp(argv[argc - 1], "--temp-on-create")) {
set_auto_create_perm(GRANT_TEMP);
argc--;
}
// ui socket should always be initialized before anything else, since it
// handles the setuid bits!
int ret = init_ui_socket(argv[argc - 1]);
if (ret != 0) {
fprintf(stderr, "Could not initalize ui-socket.\n");
fprintf(stderr, "[ICFS] Could not initalize ui-socket.\n");
exit(EXIT_FAILURE);
}
@ -49,7 +78,7 @@ int main(int argc, char *argv[]) {
ret = source_init(mountpoint);
if (ret != 0) {
perror("source_init");
perror("[ICFS] source_init");
exit(EXIT_FAILURE);
}

View File

@ -41,7 +41,7 @@ void set_db_fsuid() {
status = setfsuid(ruid);
if (status < 0) {
fprintf(stderr, "Couldn't set uid to %d.\n", ruid);
fprintf(stderr, "[ICFS] Couldn't set uid to %d.\n", ruid);
exit(status);
}
pthread_mutex_unlock(&uid_switch);
@ -56,7 +56,7 @@ void set_real_fsuid() {
status = setfsuid(ruid);
if (status < 0) {
fprintf(stderr, "Couldn't set uid to %d.\n", euid);
fprintf(stderr, "[ICFS] Couldn't set uid to %d.\n", euid);
exit(status);
}
pthread_mutex_unlock(&uid_switch);
@ -67,12 +67,13 @@ static int check_table_col_schema(void *notused, int argc, char **argv,
(void)notused;
(void)colname;
if (argc < 3) {
fprintf(stderr, "Unexpected amount of arguments given to the callback.\n");
fprintf(stderr,
"[ICFS] Unexpected amount of arguments given to the callback.\n");
return 1;
}
int column_num = atoi(argv[0]);
if (column_num >= column_count) {
fprintf(stderr, "Table contains unexpected amount of columns.\n");
fprintf(stderr, "[ICFS] Table contains unexpected amount of columns.\n");
return 1;
}
@ -80,7 +81,8 @@ static int check_table_col_schema(void *notused, int argc, char **argv,
strcmp(types[column_num], argv[2]) == 0) {
return 0;
}
fprintf(stderr, "Column %d does not conform to the schema.\n", column_num);
fprintf(stderr, "[ICFS] Column %d does not conform to the schema.\n",
column_num);
return 1;
}
@ -88,14 +90,15 @@ static int set_flag(void *flag, int argc, char **argv, char **colname) {
(void)colname;
if (argc < 3) {
fprintf(stderr,
"Unexpected amount of arguments given to the callback: %d.\n",
fprintf(
stderr,
"[ICFS] Unexpected amount of arguments given to the callback: %d.\n",
argc);
return 1;
}
if (atoi(argv[2])) {
fprintf(stderr, "Third column was: %s\n", argv[2]);
fprintf(stderr, "[ICFS] Third column was: %s\n", argv[2]);
*(int *)flag = 1;
} else {
*(int *)flag = -1;
@ -104,7 +107,7 @@ static int set_flag(void *flag, int argc, char **argv, char **colname) {
}
int create_database_schema() {
fprintf(stderr, "Creating table 'permissions'.\n");
fprintf(stderr, "[ICFS] Creating table 'permissions'.\n");
const char *create_query =
"CREATE TABLE permissions(executable TEXT NOT "
"NULL, filename TEXT NOT NULL, mode INTEGER NOT NULL);";
@ -112,12 +115,12 @@ int create_database_schema() {
int ret = sqlite3_exec(perm_database, create_query, NULL, NULL, &err);
if (ret != SQLITE_OK) {
fprintf(stderr, "sqlite3 error: %s\n", err);
fprintf(stderr, "[ICFS] sqlite3 error: %s\n", err);
sqlite3_free(err);
return 1;
}
fprintf(stderr, "Database created successfully\n");
fprintf(stderr, "[ICFS] Database created successfully\n");
return 0;
}
@ -131,14 +134,14 @@ int ensure_database_schema() {
int result = sqlite3_table_column_metadata(
perm_database, NULL, table_name, NULL, NULL, NULL, NULL, NULL, NULL);
if (result == SQLITE_ERROR) {
fprintf(stderr, "Table '%s' does not exist.\n", table_name);
fprintf(stderr, "[ICFS] Table '%s' does not exist.\n", table_name);
if (create_database_schema()) {
fprintf(stderr, "Table could not be created.\n");
fprintf(stderr, "[ICFS] Table could not be created.\n");
return 1;
}
return 0;
} else if (result != SQLITE_OK) {
fprintf(stderr, "Database metadata could not be retrieved.\n");
fprintf(stderr, "[ICFS] Database metadata could not be retrieved.\n");
return 1;
}
@ -148,12 +151,12 @@ int ensure_database_schema() {
sqlite3_exec(perm_database, pragma, check_table_col_schema, NULL, &err);
if (ret != SQLITE_OK) {
fprintf(stderr, "sqlite3 error: %s\n", err);
fprintf(stderr, "[ICFS] sqlite3 error: %s\n", err);
sqlite3_free(err);
return 1;
}
fprintf(stderr, "Schema is correct.\n");
fprintf(stderr, "[ICFS] Schema is correct.\n");
return 0;
}
@ -168,24 +171,25 @@ int init_perm_permissions_table(const char *db_filename) {
umask(0077);
ruid = getuid();
euid = geteuid();
fprintf(stderr, "Running with uid: %d, gid: %d\n", euid, getegid());
fprintf(stderr, "[ICFS] Running with uid: %d, gid: %d\n", euid, getegid());
if (sqlite3_open_v2(db_filename, &perm_database,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
SQLITE_OPEN_FULLMUTEX,
NULL)) {
perror("Can't open permanent permissions database");
perror("[ICFS] Can't open permanent permissions database");
return -1;
}
umask(0);
if (ensure_database_schema()) {
fprintf(stderr, "Database schema is not correct.\n");
fprintf(stderr, "[ICFS] Database schema is not correct.\n");
return -1;
}
int status = seteuid(ruid);
if (status < 0) {
fprintf(stderr, "Couldn't set euid to ruid during database setup.\n");
fprintf(stderr,
"[ICFS] Couldn't set euid to ruid during database setup.\n");
exit(status);
}
@ -222,7 +226,7 @@ access_t check_perm_access_noparent(const char *filename,
int step_ret = sqlite3_step(stmt);
if (step_ret != SQLITE_ROW && step_ret != SQLITE_DONE) {
fprintf(stderr, "SQLite error: %s\n", sqlite3_errstr(step_ret));
fprintf(stderr, "[ICFS] SQLite error: %s\n", sqlite3_errstr(step_ret));
sqlite3_finalize(stmt);
return ret;
}
@ -304,7 +308,7 @@ int set_perm_access(const char *filename, struct process_info pi,
// asprintf). That does not explicitly rule out that query will be a valid
// pointer. But the risk of freeing a non-allocated pointer is too much to
// justify preparing for this.
fprintf(stderr, "Could not create query on rule insertion\n");
fprintf(stderr, "[ICFS] Could not create query on rule insertion\n");
perror("");
return 1;
}
@ -313,7 +317,7 @@ int set_perm_access(const char *filename, struct process_info pi,
ret = sqlite3_exec(perm_database, query, NULL, NULL, &sqlite_error);
free(query);
if (ret != SQLITE_OK) {
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error);
fprintf(stderr, "[ICFS] SQLite returned an error: %s\n", sqlite_error);
sqlite3_free(sqlite_error);
return 1;
}

View File

@ -46,7 +46,8 @@ pid_t get_main_thread_pid(pid_t tid) {
fclose(fp);
if (tgid != tid) {
fprintf(stderr, "The tid and and pid wasn't equal. tid:%d, pid:%d.\n", tid,
fprintf(stderr,
"[ICFS] The tid and and pid wasn't equal. tid:%d, pid:%d.\n", tid,
tgid);
}
return tgid;
@ -59,7 +60,7 @@ char *get_process_name_by_pid(const int pid) {
size_t size = 128;
char *name = malloc(size);
if (name == NULL) {
fprintf(stderr, "Could not get process name by pid %d", pid);
fprintf(stderr, "[ICFS] Could not get process name by pid %d", pid);
perror("");
return NULL;
}
@ -68,7 +69,7 @@ char *get_process_name_by_pid(const int pid) {
ssize_t len = readlink(path, name, size);
if (len == -1) {
fprintf(stderr, "Could not get process name by pid %d", pid);
fprintf(stderr, "[ICFS] Could not get process name by pid %d", pid);
perror("");
free(name);
return NULL;
@ -105,7 +106,7 @@ pid_t get_parent_pid(pid_t pid) {
FILE *file = fopen(path, "r");
if (file == NULL) {
perror("Failed to open /proc/<pid>/status");
perror("[ICFS] Failed to open /proc/<pid>/status");
return 0;
}

View File

@ -32,7 +32,7 @@ const char *source_filename_translate(const char *filename) {
int source_init(const char *root_path) {
handle.mountpoint = malloc(strlen(root_path) + 1);
if (handle.mountpoint == NULL) {
perror("Malloc failed");
perror("[ICFS] Malloc failed");
return -1;
}
@ -41,7 +41,8 @@ int source_init(const char *root_path) {
int root_fd = open(root_path, O_PATH);
if (root_fd == -1) {
fprintf(stderr, "Could not initialize source file system at %s", root_path);
fprintf(stderr, "[ICFS] Could not initialize source file system at %s",
root_path);
perror("");
return -1;
}
@ -65,7 +66,7 @@ const char *real_filename(const char *filename) {
// Allocate memory (+1 for null terminator)
char *result = malloc(total_len + 1);
if (result == NULL) {
fprintf(stderr, "Memory allocation failed");
fprintf(stderr, "[ICFS] Memory allocation failed");
perror("");
return NULL;
}
@ -111,7 +112,7 @@ DIR *source_opendir(const char *filename) {
const char *relative_filename = source_filename_translate(filename);
int fd = openat(handle.root_fd, relative_filename, 0);
if (fd < 0) {
perror("Openat failed");
perror("[ICFS] Openat failed");
return NULL;
}
DIR *dir_pointer = fdopendir(fd);
@ -148,7 +149,7 @@ int source_truncate(const char *filename, off_t length) {
const char *relative_filename = source_filename_translate(filename);
int fd = openat(handle.root_fd, relative_filename, 0);
if (fd < 0) {
perror("Openat failed");
perror("[ICFS] Openat failed");
return -1;
}
return ftruncate(fd, length);

View File

@ -52,20 +52,21 @@ unsigned long long get_process_creation_time(pid_t pid) {
// Open the status file
fp = fopen(path, "r");
if (fp == NULL) {
perror("fopen");
perror("[ICFS] fopen");
return 0;
}
// Read the creation time (the 22nd field in the stat file)
for (int i = 1; i < 22; i++) {
if (fscanf(fp, "%*s") == EOF) {
fprintf(stderr, "Error reading process stat file on the number %d\n", i);
fprintf(stderr,
"[ICFS] Error reading process stat file on the number %d\n", i);
fclose(fp);
return 0;
}
}
if (fscanf(fp, "%llu", &creation_time) != 1) {
fprintf(stderr, "Error reading creation time\n");
fprintf(stderr, "[ICFS] Error reading creation time\n");
fclose(fp);
return 0;
}
@ -183,7 +184,7 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) {
if (permission_entry != NULL) {
unsigned long long process_creation_time = get_process_creation_time(pid);
if (process_creation_time == 0) {
perror("Could not retrieve process creation time");
perror("[ICFS] Could not retrieve process creation time");
pthread_rwlock_unlock(&temp_permissions_table_lock);
return NDEF;
}
@ -271,7 +272,7 @@ int set_temp_access(const char *filename, struct process_info pi,
unsigned long long process_creation_time =
get_process_creation_time(pi.PID);
if (process_creation_time == 0) {
perror("Could not retrieve process creation time");
perror("[ICFS] Could not retrieve process creation time");
pthread_rwlock_unlock(&temp_permissions_table_lock);
return -1;
}

View File

@ -45,19 +45,21 @@ int init_ui_socket(const char *perm_permissions_db_filename) {
access_log = fopen("/etc/icfs-log", "a+");
if (init_temp_permissions_table()) {
fprintf(stderr, "Could not initialize temporary permissions table.\n");
fprintf(stderr,
"[ICFS] Could not initialize temporary permissions table.\n");
return 1;
}
if (init_perm_permissions_table(perm_permissions_db_filename)) {
fprintf(stderr, "Could not initialize permanent permissions table.\n");
fprintf(stderr,
"[ICFS] Could not initialize permanent permissions table.\n");
return 1;
}
// Test if dialogue is installed (get version)
fp = popen("icfs_dialogue --version", "r");
if (fp == NULL) {
perror("Pipe returned an error");
perror("[ICFS] Pipe returned an error");
return 1;
}
@ -96,7 +98,7 @@ struct dialogue_response ask_access(const char *filename,
// asprintf). That does not explicitly rule out that command will be a valid
// pointer. But the risk of freeing a non-allocated pointer is too much to
// justify preparing for this.
fprintf(stderr, "Could not create query on rule insertion");
fprintf(stderr, "[ICFS] Could not create query on rule insertion");
perror("");
response.decision = DENY;
response.filename = malloc(2);
@ -110,7 +112,7 @@ struct dialogue_response ask_access(const char *filename,
free(command);
if (fp == NULL) {
perror("Pipe returned a error");
perror("[ICFS] Pipe returned a error");
response.decision = DENY;
response.filename = malloc(2);
response.filename[0] = '/';
@ -130,8 +132,8 @@ struct dialogue_response ask_access(const char *filename,
int dialogue_exit_code = WEXITSTATUS(pclose(fp));
fprintf(stderr, "dialogue wrote out %s\n", first(&dialogue_output));
fprintf(stderr, "dialogue returned %d\n", dialogue_exit_code);
fprintf(stderr, "[ICFS] dialogue wrote out %s\n", first(&dialogue_output));
fprintf(stderr, "[ICFS] dialogue returned %d\n", dialogue_exit_code);
if (size(&dialogue_output) == 0) {
push(&dialogue_output, '/');
@ -146,7 +148,7 @@ struct dialogue_response ask_access(const char *filename,
// assert(0 == strcmp(response.filename, first(&dialogue_output)));
cleanup(&dialogue_output);
time_t now = time(0);
fprintf(access_log, "%ld\n", now);
fprintf(access_log, "[ICFS] wrote to access log: %ld\n", now);
if (dialogue_exit_code == (DIALOGUE_YES | DIALOGUE_PERM)) {
response.decision = ALLOW;
@ -179,8 +181,9 @@ int interactive_access(const char *filename, struct process_info proc_info,
access_t access = check_temp_access(real_path, proc_info);
if (access == ALLOW) {
fprintf(stderr,
"Permission allowed to %s based on a rule present in the temp "
fprintf(
stderr,
"[ICFS] Permission allowed to %s based on a rule present in the temp "
"permission table.\n",
proc_info.name);
free(real_path);
@ -188,8 +191,9 @@ int interactive_access(const char *filename, struct process_info proc_info,
return 1;
}
if (access == DENY) {
fprintf(stderr,
"Permission denied to %s based on a rule present in the temp "
fprintf(
stderr,
"[ICFS] Permission denied to %s based on a rule present in the temp "
"permission table.\n",
proc_info.name);
free(real_path);
@ -199,8 +203,9 @@ int interactive_access(const char *filename, struct process_info proc_info,
access = check_perm_access(real_path, proc_info);
if (access == ALLOW) {
fprintf(stderr,
"Permission allowed to %s based on a rule present in the perm "
fprintf(
stderr,
"[ICFS] Permission allowed to %s based on a rule present in the perm "
"permission table.\n",
proc_info.name);
free(real_path);
@ -208,8 +213,9 @@ int interactive_access(const char *filename, struct process_info proc_info,
return 1;
}
if (access == DENY) {
fprintf(stderr,
"Permission denied to %s based on a rule present in the perm "
fprintf(
stderr,
"[ICFS] Permission denied to %s based on a rule present in the perm "
"permission table.\n",
proc_info.name);
free(real_path);
@ -221,14 +227,16 @@ int interactive_access(const char *filename, struct process_info proc_info,
// permissions are granted
if (opts & GRANT_PERM) {
fprintf(stderr, "Permission granted permanently to %s.\n", proc_info.name);
fprintf(stderr, "[ICFS] Permission granted permanently to %s.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_ALLOW);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 1;
}
if (opts & GRANT_TEMP) {
fprintf(stderr, "Permission granted temporarily to %s.\n", proc_info.name);
fprintf(stderr, "[ICFS] Permission granted temporarily to %s.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_ALLOW);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
@ -244,7 +252,7 @@ int interactive_access(const char *filename, struct process_info proc_info,
while (source_access(response.filename, F_OK)) {
// if it is invalid, just ask again.
fprintf(stderr, "Filename returned by zenty wasn't correct: %s\n",
fprintf(stderr, "[ICFS] Filename returned by zenty wasn't correct: %s\n",
response.filename);
free(response.filename);
response = ask_access(filename, proc_info);
@ -259,25 +267,29 @@ int interactive_access(const char *filename, struct process_info proc_info,
if (response.decision == ALLOW) {
fprintf(stderr,
"Permission granted permanently to %s based on zenty response.\n",
"[ICFS] Permission granted permanently to %s based on zenty "
"response.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_ALLOW);
ret = 1;
} else if (response.decision == ALLOW_TEMP) {
fprintf(stderr,
"Permission granted temporarily to %s based on zenty response.\n",
"[ICFS] Permission granted temporarily to %s based on zenty "
"response.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_ALLOW);
ret = 1;
} else if (response.decision == DENY_TEMP) {
fprintf(stderr,
"Permission denied temporarily to %s based on zenty response.\n",
fprintf(
stderr,
"[ICFS] Permission denied temporarily to %s based on zenty response.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_DENY);
ret = 0;
} else if (response.decision == DENY) {
fprintf(stderr,
"Permission denied permanently to %s based on zenty response.\n",
fprintf(
stderr,
"[ICFS] Permission denied permanently to %s based on zenty response.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_DENY);
ret = 0;

6
test/stress.bash Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
count=$1
for i in $(seq $count); do
$2 ./protected/haystack/
done

View File

@ -10,7 +10,7 @@ chmod 777 ./protected/perm777 ./protected/perm000
echo "Free code, free world." >./protected/motto
mkdir protected/haystack
for i in {1..10}; do
for i in {1..100}; do
touch "./protected/haystack/hay$i"
done
touch ./protected/haystack/needle
@ -22,7 +22,7 @@ make -C ./opener || (
echo "Could not make the opener program."
exit 1
)
for i in {1..10}; do
for i in {1..12}; do
cp ./opener/opener "./openers/opener$i"
ln --symbolic "$(realpath "./openers/opener$i")" "./openers/symlinked_opener$i"
done
@ -43,6 +43,12 @@ if [[ $1 == "--setuid" ]]; then
echo "Valgrind will not be used due to setuid compatibility issues."
../build/icfs -o default_permissions ./protected ./.pt.db &
sleep 1
elif [[ $1 == "--performance" ]]; then
echo "Database protection will not be tested due to the lack of setuid capabilites."
echo "To test it, run this script with '--setuid'."
echo "valgrind won't be used to make performance measurements more accurate."
../build/icfs -o default_permissions ./protected ./.pt.db &
sleep 5
else
echo "Database protection will not be tested due to the lack of setuid capabilites."
echo "To test it, run this script with '--setuid'."
@ -205,9 +211,36 @@ else
echo "[ICFS-TEST]: permanent permissions database access was not tested due to the lack of seuid bit setting capabilites. To test this, run the script with '--setuid' flag"
fi
RUNS_NUM=500
if [[ $1 == '--performance' ]]; then
#warmup
icfs_dialogue --set-fake-response yes
parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener10"
icfs_dialogue --set-fake-response yes
echo "[ICFS-TEST]: temp permissions"
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener7"
icfs_dialogue --set-fake-response yes_perm
echo "[ICFS-TEST]: perm permissions"
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener8"
fi
# unmount
sleep 0.5
#lsof +f -- $(realpath ./protected)
umount "$(realpath ./protected)"
sleep 2
sleep 3
if [[ $1 == '--performance' ]]; then
#warmup
parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener9"
echo "[ICFS-TEST]: bare filesystem"
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener9"
fi