10 Commits

Author SHA1 Message Date
6fd6907121 Tried making the perm permissions faster by perparing queries 2025-04-14 18:47:56 +02:00
1bc25af6f1 Added profiling logic to tests with perf 2025-04-14 18:47:32 +02:00
BritishTeapot
13fd0db8a8 Added perf artifacts to gitignore 2025-04-14 16:47:40 +02:00
BritishTeapot
55fb5c54c6 Improved code readability 2025-04-14 16:46:06 +02:00
BritishTeapot
402a5d109f Fixed incorrect executable path problem.
Previously, process name was grabbed from `/proc/pid/cmdline`. This was
revealed to be faulty, since the path to the executable might be
relative, and thus would change the result depending on how the program
was called. Also, it made executable renaming a viable bypass of the
entire access control.

I still don't fully undestand how I managed to not think of this before
:)
2025-04-12 18:44:20 +02:00
BritishTeapot
beec6f4a4c Changed tests to use the database file argument 2025-04-07 19:38:56 +02:00
BritishTeapot
16b8d77fb9 Improved code readability and added database file argument. 2025-04-07 19:38:33 +02:00
BritishTeapot
aea6e94ad7 Fixed incorrect database creation flags 2025-04-02 18:56:31 +02:00
BritishTeapot
52fcb4d4e3 Fixed an arbitrary return value in temp permissions init 2025-04-02 18:49:14 +02:00
badbf2ff98 Merge pull request 'setuid' (#7) from setuid into main
Reviewed-on: #7
2025-04-01 19:57:01 +02:00
10 changed files with 207 additions and 71 deletions

2
.gitignore vendored
View File

@@ -4,3 +4,5 @@ build/*
test/protected/* test/protected/*
test/.pt.db test/.pt.db
compile_commands.json compile_commands.json
test/perf*
test/callgraph*

View File

@@ -21,14 +21,14 @@ endif
# set up cflags and libs # set up cflags and libs
CFLAGS := -D_FILE_OFFSET_BITS=64 CFLAGS := -D_FILE_OFFSET_BITS=64 -g
LDFLAGS := LDFLAGS :=
CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES)) CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES))
LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES)) LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES))
ifeq ($(DEBUG),1) ifeq ($(DEBUG),1)
CFLAGS += -O0 -pedantic -g -Wall -Wextra -Wcast-align \ CFLAGS += -O0 -pedantic -Wall -Wextra -Wcast-align \
-Wcast-qual -Wdisabled-optimization -Wformat=2 \ -Wcast-qual -Wdisabled-optimization -Wformat=2 \
-Winit-self -Wlogical-op -Wmissing-declarations \ -Winit-self -Wlogical-op -Wmissing-declarations \
-Wmissing-include-dirs -Wredundant-decls -Wshadow \ -Wmissing-include-dirs -Wredundant-decls -Wshadow \

View File

@@ -11,6 +11,7 @@
See the file LICENSE. See the file LICENSE.
*/ */
#include <stddef.h>
#define FUSE_USE_VERSION 31 #define FUSE_USE_VERSION 31
#define _GNU_SOURCE #define _GNU_SOURCE
@@ -39,23 +40,58 @@
#include "sourcefs.h" #include "sourcefs.h"
#include "ui-socket.h" #include "ui-socket.h"
// TODO: move this to other file
const char *get_process_name_by_pid(const int pid) { const char *get_process_name_by_pid(const int pid) {
char *name = (char *)calloc(1024, sizeof(char)); char path[1024];
if (name) { sprintf(path, "/proc/%d/exe", pid);
sprintf(name, "/proc/%d/cmdline", pid);
FILE *f = fopen(name, "r"); char *name = realpath(path, NULL);
if (f) { if (name == NULL) {
size_t size; fprintf(stderr, "Could not get process name by pid %d", pid);
size = fread(name, sizeof(char), 1024, f); perror("");
if (size > 0) {
if ('\n' == name[size - 1])
name[size - 1] = '\0';
} }
fclose(f);
/*
size_t namelen = 32;
ssize_t readret = 0;
char *name = NULL;
while (namelen >= (size_t)readret && readret > 0) {
namelen *= 2;
name = calloc(namelen, sizeof(char));
if (name == NULL) {
free(path);
fprintf(stderr, "Could not get get process name by pid %d", pid);
perror("");
return NULL;
}
readret = readlink(path, name, namelen);
if (readret < 0) {
free(name);
free(path);
fprintf(stderr, "Couldn't get process name by pid %d", pid);
perror("");
return NULL;
}
if (namelen >= (size_t)readret) {
free(name);
} }
} }
*/
return name; return name;
/*
FILE *file = fopen(path, "r");
if (file) {
size_t size = 0;
size = fread(path, sizeof(char), 1024, file);
if (size > 0) {
if ('\n' == path[size - 1]) {
path[size - 1] = '\0';
}
}
fclose(file);
}
*/
} }
// TODO: move this somewhere else // TODO: move this somewhere else
@@ -70,8 +106,8 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
To make parallel_direct_writes valid, need either set cfg->direct_io To make parallel_direct_writes valid, need either set cfg->direct_io
in current function (recommended in high level API) or set fi->direct_io in current function (recommended in high level API) or set fi->direct_io
in xmp_create() or xmp_open(). */ in xmp_create() or xmp_open(). */
// cfg->direct_io = 1; cfg->direct_io = 1;
// cfg->parallel_direct_writes = 1; cfg->parallel_direct_writes = 1;
/* Pick up changes from lower filesystem right away. This is /* Pick up changes from lower filesystem right away. This is
also necessary for better hardlink support. When the kernel also necessary for better hardlink support. When the kernel
@@ -83,18 +119,19 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
cfg->entry_timeout = 0; cfg->entry_timeout = 0;
cfg->attr_timeout = 0; cfg->attr_timeout = 0;
cfg->negative_timeout = 0; cfg->negative_timeout = 0;
fprintf(stderr, "%d\n", getpid());
return NULL; return NULL;
} }
static int xmp_getattr(const char *path, struct stat *stbuf, static int xmp_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi) { struct fuse_file_info *file_info) {
int res; int res;
(void)path; (void)path;
if (fi) if (file_info)
res = fstat(fi->fh, stbuf); res = fstat(file_info->fh, stbuf);
else else
res = source_stat(path, stbuf); res = source_stat(path, stbuf);
if (res == -1) { if (res == -1) {
@@ -106,38 +143,39 @@ static int xmp_getattr(const char *path, struct stat *stbuf,
} }
static int xmp_access(const char *path, int mask) { static int xmp_access(const char *path, int mask) {
int res; int res = -1;
// if mask is F_OK, then we don't need to check the permissions // if mask is F_OK, then we don't need to check the permissions
// (is that possible?) // (is that possible?)
if (mask != F_OK) { if (mask != F_OK) {
struct process_info pi; struct process_info proc_info;
struct fuse_context *fc = fuse_get_context(); struct fuse_context *context = fuse_get_context();
pi.PID = fc->pid; proc_info.PID = context->pid;
pi.name = get_process_name_by_pid(pi.PID); proc_info.name = get_process_name_by_pid(proc_info.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(real_filename(path), pi, 0)) { if (!interactive_access(real_filename(path), proc_info, 0)) {
free(pi.name); free((void *)proc_info.name);
return -EACCES; return -EACCES;
} }
free(pi.name); free((void *)proc_info.name);
} }
res = source_access(path, mask); res = source_access(path, mask);
if (res == -1) if (res == -1) {
return -errno; return -errno;
}
return 0; return 0;
} }
static int xmp_readlink(const char *path, char *buf, size_t size) { static int xmp_readlink(const char *path, char *buf, size_t size) {
int res; int res = -1;
res = readlink(path, buf, size - 1); res = readlink(path, buf, size - 1);
if (res == -1) if (res == -1)
@@ -264,17 +302,18 @@ static int xmp_mknod(const char *path, mode_t mode, dev_t rdev) {
*/ */
static int xmp_mkdir(const char *path, mode_t mode) { static int xmp_mkdir(const char *path, mode_t mode) {
int res; int res = -1;
res = source_mkdir(path, mode); res = source_mkdir(path, mode);
if (res == -1) if (res == -1) {
return -errno; return -errno;
}
return 0; return 0;
} }
static int xmp_unlink(const char *path) { static int xmp_unlink(const char *path) {
int res; int res = -1;
struct process_info pi; struct process_info pi;
struct fuse_context *fc = fuse_get_context(); struct fuse_context *fc = fuse_get_context();

View File

@@ -10,16 +10,15 @@
See the file LICENSE. See the file LICENSE.
*/ */
#include <sys/types.h>
#include <unistd.h>
#define FUSE_USE_VERSION 31 #define FUSE_USE_VERSION 31
#define _GNU_SOURCE #define _GNU_SOURCE
#include <fuse3/fuse.h> #include <fuse3/fuse.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include "fuse_operations.h" #include "fuse_operations.h"
#include "sourcefs.h" #include "sourcefs.h"
@@ -28,15 +27,25 @@
const char *mountpoint = NULL; const char *mountpoint = NULL;
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: icfs <FUSE arguments> [target directory] [path to "
"the permanent permissions database\n");
return EXIT_FAILURE;
}
// if umask != 0, the filesystem will create files with more restrictive
// permissions than it's caller reqested
umask(0); umask(0);
int ret = init_ui_socket(); // 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) { if (ret != 0) {
fprintf(stderr, "Could not initalize ui-socket.\n"); fprintf(stderr, "Could not initalize ui-socket.\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
mountpoint = realpath(argv[argc - 1], NULL); mountpoint = realpath(argv[argc - 2], NULL);
ret = source_init(mountpoint); ret = source_init(mountpoint);
if (ret != 0) { if (ret != 0) {
@@ -44,9 +53,9 @@ int main(int argc, char *argv[]) {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
ret = fuse_main(argc, argv, get_fuse_operations(), NULL); ret = fuse_main(argc - 1, argv, get_fuse_operations(), NULL);
free(mountpoint); free((void *)mountpoint);
destroy_ui_socket(); destroy_ui_socket();
return ret; return ret;
} }

View File

@@ -27,6 +27,7 @@ const int column_count = 2;
const char *const schema[] = {"executable", "filename"}; const char *const schema[] = {"executable", "filename"};
const char *const types[] = {"TEXT", "TEXT"}; const char *const types[] = {"TEXT", "TEXT"};
uid_t ruid, euid, current_pid; uid_t ruid, euid, current_pid;
sqlite3_stmt *perm_check_statement = NULL;
pthread_mutex_t uid_switch = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t uid_switch = PTHREAD_MUTEX_INITIALIZER;
void set_db_fsuid() { void set_db_fsuid() {
@@ -141,6 +142,45 @@ int ensure_database_schema() {
return 0; return 0;
} }
int prepare_sql_queries() {
const char *query_template =
"SELECT * FROM %s WHERE executable = ? AND filename = ?;";
char *query_string = NULL;
int query_len = snprintf(NULL, 0, query_template, table_name) + 1;
if (query_len < 0) {
fprintf(stderr, "Failed to prepare statement");
perror("");
return 1;
}
query_string = malloc(query_len);
if (query_string == NULL) {
fprintf(stderr, "Failed to allocate memory for the query");
perror("");
return 1;
}
int ret = snprintf(query_string, query_len, query_template, table_name);
if (ret < 0) {
fprintf(stderr, "Failed to prepare statement");
perror("");
free(query_string);
return 1;
}
if (sqlite3_prepare_v2(perm_database, query_string, -1, &perm_check_statement,
NULL) != SQLITE_OK) {
fprintf(stderr, "Failed to prepare statement: %s\n",
sqlite3_errmsg(perm_database));
free(query_string);
return 1;
}
free(query_string);
return 0;
}
void free_sql_queries(void) { sqlite3_finalize(perm_check_statement); }
/** /**
* Initializes the permanent permissions table. * Initializes the permanent permissions table.
* *
@@ -154,8 +194,11 @@ int init_perm_permissions_table(const char *db_filename) {
euid = geteuid(); euid = geteuid();
fprintf(stderr, "Running with uid: %d, gid: %d\n", euid, getegid()); fprintf(stderr, "Running with uid: %d, gid: %d\n", euid, getegid());
if (sqlite3_open(db_filename, &perm_database)) { if (sqlite3_open_v2(db_filename, &perm_database,
perror("Can't open permanent permissions database:"); SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
SQLITE_OPEN_FULLMUTEX,
NULL)) {
perror("Can't open permanent permissions database");
return -1; return -1;
} }
umask(0); umask(0);
@@ -166,17 +209,24 @@ int init_perm_permissions_table(const char *db_filename) {
int status = seteuid(ruid); int status = seteuid(ruid);
if (status < 0) { if (status < 0) {
fprintf(stderr, "Couldn't set euid to ruid during database setup.\n"); fprintf(stderr, "Couldn't set euid to ruid.\n");
exit(status); exit(status);
} }
if (prepare_sql_queries()) {
fprintf(stderr, "Couldn't prepare sql queries.\n");
exit(status);
}
return 0; return 0;
} }
/** /**
* Destroys the permanent permissions table. * Destroys the permanent permissions table.
*/ */
void destroy_perm_permissions_table() { sqlite3_close(perm_database); } void destroy_perm_permissions_table(void) {
free_sql_queries();
sqlite3_close(perm_database);
}
/** /**
* Checks if the process has a permanent access to the file. * Checks if the process has a permanent access to the file.

View File

@@ -75,6 +75,7 @@ unsigned long long get_process_creation_time(pid_t pid) {
int init_temp_permissions_table() { int init_temp_permissions_table() {
pthread_mutex_init(&temp_permissions_table_lock, PTHREAD_MUTEX_DEFAULT); pthread_mutex_init(&temp_permissions_table_lock, PTHREAD_MUTEX_DEFAULT);
init(&temp_permissions_table); init(&temp_permissions_table);
return 0;
} }
/** /**
@@ -225,7 +226,6 @@ int give_temp_access(const char *filename, struct process_info pi) {
push(&new_permission_entry.allowed_files, strdup(filename)); push(&new_permission_entry.allowed_files, strdup(filename));
insert(&temp_permissions_table, pi.PID, new_permission_entry); insert(&temp_permissions_table, pi.PID, new_permission_entry);
printf("temp_permissions_table size: %ld\n", size(&temp_permissions_table));
pthread_mutex_unlock(&temp_permissions_table_lock); pthread_mutex_unlock(&temp_permissions_table_lock);
return 0; return 0;

View File

@@ -13,7 +13,6 @@
#include "perm_permissions_table.h" #include "perm_permissions_table.h"
#include "temp_permissions_table.h" #include "temp_permissions_table.h"
#include "ui-socket.h" #include "ui-socket.h"
#include <errno.h>
#include <pthread.h> #include <pthread.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -22,17 +21,17 @@
#include <sys/un.h> #include <sys/un.h>
#include <unistd.h> #include <unistd.h>
int init_ui_socket() { #define ZENITY_TEMP_ALLOW_MESSAGE "Allow this time\n"
char line[256];
FILE *fp; int init_ui_socket(const char *perm_permissions_db_filename) {
FILE *fp = NULL;
if (init_temp_permissions_table()) { if (init_temp_permissions_table()) {
fprintf(stderr, "Could not initialize temporary permissions table.\n"); fprintf(stderr, "Could not initialize temporary permissions table.\n");
return 1; return 1;
} }
if (init_perm_permissions_table( if (init_perm_permissions_table(perm_permissions_db_filename)) {
"/home/fedir/Developement/uni/ICFS/test/.pt.db")) {
fprintf(stderr, "Could not initialize permanent permissions table.\n"); fprintf(stderr, "Could not initialize permanent permissions table.\n");
return 1; return 1;
} }
@@ -44,13 +43,11 @@ int init_ui_socket() {
return 1; return 1;
} }
while (fgets(line, sizeof(line), fp))
printf("%s", line);
pclose(fp); pclose(fp);
return 0; return 0;
} }
void destroy_ui_socket() { void destroy_ui_socket(void) {
destroy_temp_permissions_table(); destroy_temp_permissions_table();
destroy_perm_permissions_table(); destroy_perm_permissions_table();
} }
@@ -64,16 +61,16 @@ void destroy_ui_socket() {
* @return: 0 if access is denied, 1 if access is allowed, 2 if access is * @return: 0 if access is denied, 1 if access is allowed, 2 if access is
* allowed for the runtime of the process * allowed for the runtime of the process
*/ */
int ask_access(const char *filename, struct process_info pi) { int ask_access(const char *filename, struct process_info proc_info) {
FILE *fp; FILE *fp = NULL;
size_t command_len = size_t command_len =
139 + sizeof(pid_t) * 8 + strlen(pi.name) + strlen(filename); 139 + sizeof(pid_t) * 8 + strlen(proc_info.name) + strlen(filename);
char *command = (char *)malloc(command_len); char *command = (char *)malloc(command_len);
snprintf(command, command_len, snprintf(command, command_len,
"zenity --question --extra-button \"Allow this time\" --title " "zenity --question --extra-button \"Allow this time\" --title "
"\"Allow Access?\" --text \"Allow process " "\"Allow Access?\" --text \"Allow process "
"<tt>%s</tt> with PID <tt>%d</tt> to access <tt>%s</tt>\"", "<tt>%s</tt> with PID <tt>%d</tt> to access <tt>%s</tt>\"",
pi.name, pi.PID, filename); proc_info.name, proc_info.PID, filename);
// Zenity Question Message Popup // Zenity Question Message Popup
fp = popen(command, "r"); fp = popen(command, "r");
@@ -87,10 +84,10 @@ int ask_access(const char *filename, struct process_info pi) {
// if the user clicks the "Allow this time" button, `zenity` will only // if the user clicks the "Allow this time" button, `zenity` will only
// write it to `stdout`, but the exit code will still be `1`. So, we need // write it to `stdout`, but the exit code will still be `1`. So, we need
// to manually check the output. // to manually check the output.
char buffer[1024]; char buffer[sizeof(ZENITY_TEMP_ALLOW_MESSAGE) + 1];
while (fgets(buffer, sizeof(buffer), fp)) { while (fgets(buffer, sizeof(buffer), fp)) {
printf("%s", buffer); printf("%s", buffer);
if (strcmp(buffer, "Allow this time\n") == 0) { if (strcmp(buffer, ZENITY_TEMP_ALLOW_MESSAGE) == 0) {
pclose(fp); pclose(fp);
return 2; return 2;
} }
@@ -117,9 +114,11 @@ int ask_access(const char *filename, struct process_info pi) {
* @param opts: options (GRANT_TEMP, GRANT_PERM) * @param opts: options (GRANT_TEMP, GRANT_PERM)
* @return: 0 if access is denied, 1 if access is allowed * @return: 0 if access is denied, 1 if access is allowed
*/ */
int interactive_access(const char *filename, struct process_info pi, int opts) { int interactive_access(const char *filename, struct process_info proc_info,
int opts) {
if (check_temp_access(filename, pi) || check_perm_access(filename, pi)) { if (check_temp_access(filename, proc_info) ||
check_perm_access(filename, proc_info)) {
// access was already granted before // access was already granted before
return 1; return 1;
} }
@@ -128,22 +127,24 @@ int interactive_access(const char *filename, struct process_info pi, int opts) {
// permissions are granted // permissions are granted
if (opts & GRANT_PERM) { if (opts & GRANT_PERM) {
give_perm_access(filename, pi); give_perm_access(filename, proc_info);
return 1; return 1;
} }
if (opts & GRANT_TEMP) { if (opts & GRANT_TEMP) {
give_temp_access(filename, pi); give_temp_access(filename, proc_info);
return 1; return 1;
} }
int user_response = ask_access(filename, pi); int user_response = ask_access(filename, proc_info);
if (user_response == 1) { if (user_response == 1) {
// user said "yes" // user said "yes"
give_perm_access(filename, pi); give_perm_access(filename, proc_info);
return 1; return 1;
} else if (user_response == 2) { }
if (user_response == 2) {
// user said "yes, but only this time" // user said "yes, but only this time"
give_temp_access(filename, pi); give_temp_access(filename, proc_info);
return 1; return 1;
} }

View File

@@ -21,7 +21,7 @@
* *
* @return: 0 on success, -1 on faliure. * @return: 0 on success, -1 on faliure.
*/ */
int init_ui_socket(void); int init_ui_socket(const char *perm_permissions_db_filename);
/** /**
* Close the GUI communication. * Close the GUI communication.

View File

@@ -12,10 +12,17 @@ else
if [[ $FAKE_ZENITY_RESPONSE == "yes_tmp" ]]; then if [[ $FAKE_ZENITY_RESPONSE == "yes_tmp" ]]; then
printf "Allow this time\n" printf "Allow this time\n"
exit 1 exit 1
elif [[ $FAKE_ZENITY_RESPONSE == "yes_tmp_alt" ]]; then
printf "Allow this time\n"
echo "yes_alt" >~/.fake_zenity_response
exit 1
elif [[ $FAKE_ZENITY_RESPONSE == "no" ]]; then elif [[ $FAKE_ZENITY_RESPONSE == "no" ]]; then
exit 1 exit 1
elif [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then elif [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then
exit 0 exit 0
elif [[ $FAKE_ZENITY_RESPONSE == "yes_alt" ]]; then
echo "yes_tmp_alt" >~/.fake_zenity_response
exit 0
fi fi
fi fi
fi fi

View File

@@ -23,12 +23,20 @@ if [[ $1 == "--setuid" ]]; then
sudo chown icfs: ../build/icfs && sudo chmod 4777 ../build/icfs sudo chown icfs: ../build/icfs && sudo chmod 4777 ../build/icfs
chmod g+w . # needed for icfs to be able to create the database chmod g+w . # needed for icfs to be able to create the database
echo "Valgrind will not be used due to setuid compatibility issues." echo "Valgrind will not be used due to setuid compatibility issues."
../build/icfs -o default_permissions ./protected & ../build/icfs -o default_permissions ./protected ./.pt.db &
sleep 1 sleep 1
elif [[ $1 == "--perf" ]]; then
echo "Profiling with perf..."
../build/icfs -o default_permissions ./protected ./.pt.db &
echo "Profiling will require root privilieges."
sleep 3
echo "Attaching to $(pgrep icfs)"
sudo perf record -g -e cycles:u --call-graph dwarf -p $(pgrep icfs) &
sleep 10
else else
echo "Database protection will not be tested due to the lack of setuid capabilites." echo "Database protection will not be tested due to the lack of setuid capabilites."
echo "To test it, run this script with '--setuid'." echo "To test it, run this script with '--setuid'."
valgrind -s ../build/icfs -o default_permissions ./protected & valgrind --leak-check=full -s ../build/icfs -o default_permissions ./protected ./.pt.db &
sleep 5 sleep 5
fi fi
@@ -126,9 +134,29 @@ else
echo "[ICFS-TEST]: OK" echo "[ICFS-TEST]: OK"
fi fi
if [[ $1 == "--perf" ]]; then
zenity --set-fake-response yes_tmp
rm -rf ./protected/*
zenity --set-fake-response yes_alt
bonnie++ -p 4
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null &
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null &
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null &
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null
bonnie++ -p -1
fi
# unmount # unmount
sleep 0.5 sleep 0.5
#lsof +f -- $(realpath ./protected) #lsof +f -- $(realpath ./protected)
umount $(realpath ./protected) umount $(realpath ./protected)
sleep 0.5 sleep 0.5
if [[ $1 == "--perf" ]]; then
mv ./callgraph.png ./callgraph_old.png
real_user=$USER
sudo chown "$real_user" ./perf.data
perf script --dsos=icfs | gprof2dot -f perf | dot -Tpng -o callgraph.png
echo "Profile graph was written to \"callgraph.png\""
fi