diff --git a/README.md b/README.md index aaa49f8..a38e61a 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,14 @@ to the target application. The key features of `urunit` are: -- Parsing and grouping multi-word arguments from Linux kernel boot parameters -- Launching and waiting for the target application until it terminates -- Reaping all zombie processes +- Parsing and grouping multi-word arguments from Linux kernel boot parameters. +- Launching and waiting for the target application until it terminates. +- Reaping all zombie processes. +- Setting the default route to eth0, if `URUNIT_DEFROUTE` environment variable + is set. +- Reading and setting the environment variables for an application from a file. +- Reading and setting the execution environment configuration for a process from a file. +- Reading and mounting attached block devices defined in the configuration file. ## Building @@ -57,6 +62,38 @@ urunit echo `hello world` will result in `echo` receiving a single argument: `hello world`. +### Urunit configuration file + +In order to configure the execution environment for an application, `urunit` +accepts a specific configuration file. The configuration file can contain the +following information: + +- The list of the environment variables to set for the application +- The configuration for the process execution environment. +- The list of mounts of block devices. Each block device is defined by its + serial id and it will get mounted in the defined mountpoint. + +The file can be specified to `urunit` setting the `URUNIT_CONFIG` +environment variable with the path to the configuration file. + +The supported format of the configuration file is the following: + +``` +UES +/* list of environment variables */ +UEE +UCS +UID: +GID: +WD: +UCE +UBS +ID: +MP: +... +UBE +``` + ## Installation Using one of the following methods, we can install `urunit` either in a diff --git a/main.c b/main.c index 9b7347d..1885eb1 100644 --- a/main.c +++ b/main.c @@ -52,6 +52,7 @@ #include #include +#include #include #include #include @@ -63,6 +64,7 @@ #define STATUS_MAX 255 #define STATUS_MIN 0 #define ETH0_IF "eth0" +#define SERIAL_MAX_SZ 10 #ifdef DEBUG #define SHOW_DEBUG 1 @@ -82,10 +84,16 @@ struct process_config { char *wdir; }; +struct block_config { + char *id; + char *mountpoint; +}; + struct app_exec_config { char **envs; char *path_env; struct process_config *pr_conf; + struct block_config **blk_conf; }; extern char **environ; @@ -357,6 +365,8 @@ size_t measure_tokens(char *str_buf, size_t max_size, char tok) { // will point to the beginning of that string inside the list. char **parse_envs(char **string_area, size_t max_sz, char **path_env) { size_t total_envs = 0; + // TODO: We might need to retrun a list here with the first + // element being NULL instead of returning NULL char **env_vars = NULL; uint8_t path_found = 0; char *tmp_env = NULL; @@ -413,7 +423,7 @@ char **parse_envs(char **string_area, size_t max_sz, char **path_env) { free(env_vars); return NULL; } - // Add nULL to indicate the end of the table with environment variables. + // Add NULL to indicate the end of the table with environment variables. env_vars[i] = NULL; return env_vars; @@ -515,13 +525,14 @@ int get_string_val(char *str, char **value) { // // Arguments: // 1. string_area: The list with in the aformentioned format. +// 2. max_sz: The max possible size of the list. // // Return value: // On success it returns a pointer to a dynamically allocated memory that // contains a process_config struct filled with the information // from the configuration. // Otherwise, NULL is returned -struct process_config *parse_process_config(char **string_area) { +struct process_config *parse_process_config(char **string_area, size_t max_sz) { struct process_config *conf = NULL; char *tmp_field = NULL; @@ -538,7 +549,7 @@ struct process_config *parse_process_config(char **string_area) { // Also, it is safe to call strtok, even if there was no '\n', since it will // return NULL again. tmp_field = strtok(NULL, "\n"); - while (tmp_field) { + while (tmp_field && ((size_t)(tmp_field - *string_area) < max_sz)) { int ret = 0; if (memcmp(tmp_field, "UID", 3) == 0) { @@ -571,6 +582,131 @@ struct process_config *parse_process_config(char **string_area) { return NULL; } +// parse_block_config Parses a list with the following format: +// UBS +// ID: +// MP: +// ... +// UBE +// It is important to note, that this function will alter the given list, +// replacing the new line characters with the end of string '\0' character. +// The funtion returns a dynamically allocated memory and the caller is +// responsible to free that memory. +// +// Arguments: +// 1. string_area: The list with the aformentioned format. +// 2. max_sz: The maximum size of the area to look for block config +// +// Return value: +// On success it returns an array of block_config structs filled with the information +// from the list. +// Otherwise, NULL is returned +struct block_config **parse_block_config(char **string_area, size_t max_sz) { + // TODO: We might need to retrun a list here with the first + // element being NULL instead of returning NULL + struct block_config **bentries = NULL; + char *tmp_field = NULL; + size_t i = 0; + size_t total_entries = 0; + + // Count the new line characters we have in the list. + // Since every block entry consist of 2 fields, the total number + // of entries derives from diving the number of new lines by 2. + total_entries = measure_tokens(*string_area, max_sz, '\n') / 2; + // If the list is correctly formatted it will start with "UBS" + // and end with "UBE". These special strings will not be stored, + // but they add up in the overall size, since they occupy one line each. + // However, we can use this extra entry in the array to mark the end of + // the array with NULL. + bentries = malloc(total_entries * sizeof(struct block_config *)); + if (!bentries) { + fprintf(stderr, "Failed to allocate memory for block entries\n"); + return NULL; + } + if (total_entries > 0) + bentries[0] = NULL; + DEBUG_PRINTF("Found %ld block entries\n", total_entries); + + tmp_field = strtok(*string_area, "\n"); + // Discard the first string since it is the special string "UBS" + // Also, it is safe to call strtok, even if there was no '\n', since it will + // return NULL again. + tmp_field = strtok(NULL, "\n"); + while (tmp_field && i < total_entries) { + int ret = 0; + + // The first string should be "ID:" + if (memcmp(tmp_field, "ID:", 3) == 0) { + // If bentries[i] is not NULL then we never reached found + // MP entry in the config for this ID. + if (bentries[i]) { + fprintf(stderr, "Multiple ID entries without MP\n"); + goto parse_block_config_free; + } + bentries[i] = malloc(sizeof(struct block_config)); + if (!bentries[i]) { + fprintf(stderr, "Failed to allocate memory for a block entry\n"); + goto parse_block_config_free; + } + bentries[i]->id = NULL; + bentries[i]->mountpoint = NULL; + + ret = get_string_val(tmp_field, &(bentries[i]->id)); + if (ret != 0) { + fprintf(stderr, "Failed to retrieve block ID from %s\n", tmp_field); + free(bentries[i]); + goto parse_block_config_free; + } + DEBUG_PRINTF("Found block entry with ID %s\n", bentries[i]->id); + } else if (memcmp(tmp_field, "MP:", 3) == 0) { + ret = get_string_val(tmp_field, &(bentries[i]->mountpoint)); + if (ret != 0) { + fprintf(stderr, "Failed to retrieve block mountpoint from %s\n", tmp_field); + // Remove the current entry + // because it was not properly formatted. + free(bentries[i]); + goto parse_block_config_free; + } + DEBUG_PRINTF("Found block entry with MP %s\n", bentries[i]->mountpoint); + i++; + bentries[i] = NULL; + } else if (memcmp(tmp_field, "UBE", 3) == 0) { + // 4 bytes for the "UBE" string + *string_area = tmp_field + 4; + break; + } + tmp_field = strtok(NULL, "\n"); + } + + // Special case where malloc did not return NULL with 0 size, + // or none properly formatted block entries were found + // Both cases mean that we have no block entries and hence + // we should return NULL. + if (i == 0) { + // free is safe here, since bentries come from malloc and + // contains either NULL or an address. Both cases are fine for free. + free(bentries); + return NULL; + } + // In case of a malformed block config where we had an ID but no MP, + // then mountpoint will be NULL and we should free the allocated entry. + if (bentries[i] && !(bentries[i]->mountpoint)) { + free(bentries[i]); + } + // Add NULL to indicate the end of the table with block entries + bentries[i] = NULL; + + return bentries; + +parse_block_config_free: + for (size_t j = 0; j < i; j++) { + free(bentries[j]); + } + free(bentries); + + return NULL; +} + // get_config_from_file: Reads the contents of argumen and it parses the // app execution configuration and environment variables list. // The app execution configuration list starts with the line "UCS" and ends with the @@ -594,6 +730,7 @@ struct app_exec_config *get_config_from_file(char *file, char **sbuf) { char *path_env = NULL; struct app_exec_config *econf = NULL; struct process_config *pconf = NULL; + struct block_config **bconf = NULL; char *conf_area = NULL; buf = read_file_and_size(file, &size); @@ -622,6 +759,9 @@ struct app_exec_config *get_config_from_file(char *file, char **sbuf) { fprintf(stderr, "Invalid format of environment variable list. \"UEE\" was not found\n"); goto get_env_vars_error_free; } + // Reduce the size of the config by the bytes parsed + // for the environment variables list. + size -= conf_area - init_conf_area; } DEBUG_PRINT("Checking for execution environment configuration\n"); @@ -631,7 +771,7 @@ struct app_exec_config *get_config_from_file(char *file, char **sbuf) { if (memcmp(conf_area, "UCS", 3) == 0) { char *init_conf_area = conf_area; // Extract the environment variables from the list - pconf = parse_process_config(&conf_area); + pconf = parse_process_config(&conf_area, size); if (!pconf ) { fprintf(stderr, "Warning: No configuration for the application execution environment was found\n"); } @@ -643,6 +783,30 @@ struct app_exec_config *get_config_from_file(char *file, char **sbuf) { fprintf(stderr, "Invalid format of application execution environment configuration\n"); goto get_env_vars_error_free; } + // Reduce the size of the config by the bytes parsed + // for the environment variables list. + size -= conf_area - init_conf_area; + } + + DEBUG_PRINT("Checking for block volumes mount configuration\n"); + // Check if the special string "UBS" is present + // which means that now starts the configuration for the block mounts + if (memcmp(conf_area, "UBS", 3) == 0) { + char *init_conf_area = conf_area; + // Extract the block configuration + bconf = parse_block_config(&conf_area, size); + if (!bconf ) { + fprintf(stderr, "Warning: No configuration for block mounts\n"); + } + // If the list was properly formatted, ending with "UBE" + // then conf_area should differ from init_conf_area + // Otherwise, the list was not properly formatted and + // we abort the parsing. + if (conf_area == init_conf_area) { + fprintf(stderr, "Invalid format of block volume mounts\n"); + goto get_env_vars_error_free; + } + size -= conf_area - init_conf_area; } econf = malloc(sizeof(struct app_exec_config)); @@ -655,6 +819,7 @@ struct app_exec_config *get_config_from_file(char *file, char **sbuf) { econf->envs = env_vars; econf->path_env = path_env; econf->pr_conf = pconf; + econf->blk_conf = bconf; return econf; get_env_vars_error_free: @@ -848,6 +1013,365 @@ int setup_exec_env(struct process_config *process_conf) { return 0; } +// rm_empty_dirs: Removes the directory dir given as argument and all empty parent +// directories up to and including top_dir. +// +// Arguments: +// 1. dir: The directory to remove. +// 2. top_dir: The top-most directory to remove. It should not end in '/' +// +// Return value: +// On success 0 is returned. +// Otherwise -1 is returned. +int rm_empty_dirs(const char *dir, const char *top_dir) { + char current[PATH_MAX] = { 0 }; + int ret = 0; + + ret = snprintf(current, sizeof(current), "%s", dir); + if (ret <= 0 || (size_t)ret > sizeof(current)) { + fprintf(stderr, "Could not copy %s\n", dir); + return -1; + } + // Make sure the path does not end in '/' + if (current[ret - 1] == '/') { + current[ret - 1] = '\0'; + } + + DEBUG_PRINTF("Top most directory to remove: %s\n", top_dir); + while (strcmp(current, top_dir) != 0) { + char *last_slash = NULL; + + // Stop at root or common mount points + if (strcmp(current, "/") == 0 || + strcmp(current, "/mnt") == 0 || + strcmp(current, "/var") == 0 || + strcmp(current, "/home") == 0 || + strcmp(current, "/tmp") == 0) { + break; + } + + DEBUG_PRINTF("Trying to remove directory: %s\n", current); + ret = rmdir(current); + if (ret != 0) { + perror("rmdir"); + return -1; + } + DEBUG_PRINTF("Removed empty directory: %s\n", current); + + // Get parent directory + last_slash = strrchr(current, '/'); + if (!last_slash || last_slash == current) { + fprintf(stderr, "Could not get parent directory of %s\n", current); + return -1; + } + *last_slash = '\0'; + } + + DEBUG_PRINTF("Trying to remove directory: %s\n", current); + // Remove also top most directory + ret = rmdir(current); + if (ret != 0) { + perror("rmdir"); + return -1; + } + DEBUG_PRINTF("Removed empty directory: %s\n", current); + + return 0; +} + +// mkdir_all: Creates a directory path including all non-existing parent directories. +// Similar to MkdirAll in Go and "mkdir -p" command. +// +// Arguments: +// 1. path: The full path of the directory to create +// 2. mode: The permissions mode for the new directories +// +// Return value: +// On success 0 is returned. +// Otherwise -1 is returned. +int mkdir_all(const char *path, mode_t mode, char *first_dir) { + char tmp_path[PATH_MAX] = { 0 }; + char *next_slash = NULL; + int ret = 0; + struct stat st; + uint8_t is_first = 1; + size_t or_path_len = 0; + size_t tmp_len = 0; + + if (path == NULL || *path == '\0') { + fprintf(stderr, "Invalid path value\n"); + return -1; + } + + if (strcmp(path, "/") == 0) { + fprintf(stderr, "Invalid path value: %s\n", path); + return -1; + } + + // Check if path already exists + if (stat(path, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + return 0; + } else { + fprintf(stderr, "%s exists and is not a directory\n", path); + return -1; + } + } + + ret = snprintf(tmp_path, sizeof(tmp_path), "%s", path); + if (ret <= 0 || (size_t)ret > sizeof(tmp_path)) { + fprintf(stderr, "Could not create a copy of %s\n", path); + return -1; + } + + or_path_len = strlen(tmp_path); + tmp_len = or_path_len; + // Remove trailing slashes + while (tmp_len > 1 && tmp_path[tmp_len - 1] == '/') { + tmp_path[tmp_len - 1] = '\0'; + tmp_len--; + } + // We will need to copy tmp_path later so we need to include + // the end of string character. + or_path_len++; + + // Iterate through the path and create directories + next_slash = strchr(tmp_path + 1, '/'); + while(next_slash) { + *next_slash = '\0'; // Temporarily truncate + + // Try to create the directory + DEBUG_PRINTF("Trying to create dir %s\n", tmp_path); + ret = mkdir(tmp_path, mode); + if (ret != 0 && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s\n", tmp_path); + perror("mkdir"); + ret = -1; + goto mkdir_all_cleanup; + } + if (ret == 0 && is_first) { + ret = snprintf(first_dir, or_path_len, "%s", tmp_path); + if (ret < 0 || (size_t)ret > or_path_len) { + fprintf(stderr, "Could not copy first created path %s", tmp_path); + return -1; + } + is_first = 0; + } + *next_slash = '/'; // Restore the slash + next_slash = strchr(next_slash + 1, '/'); + } + + // Create the final directory + DEBUG_PRINTF("Trying to create dir %s\n", tmp_path); + ret = mkdir(tmp_path, mode); + if (ret != 0 && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s\n", tmp_path); + perror("mkdir"); + ret = -1; + goto mkdir_all_cleanup; + } + if (ret == 0 && is_first) { + ret = snprintf(first_dir, or_path_len, "%s", tmp_path); + if (ret < 0 || (size_t)ret > or_path_len) { + fprintf(stderr, "Could not copy first created directory %s\n", tmp_path); + return -1; + } + } + DEBUG_PRINTF("Top most created dir %s\n", first_dir); + return 0; + +mkdir_all_cleanup: + // If we have not created any directory yet then is_first will be 1 + // and hence we do not have to remove any directory. + if (!is_first) { + int ret = 0; + // However, the fail took place for the tmp_path directory + // which was not created and hence we do not have to remove it. + // Therefore, move to the parent directory. + char *last_slash = strrchr(tmp_path, '/'); + if (!last_slash || last_slash == tmp_path) { + fprintf(stderr, "Could not get parent directory of %s\n", tmp_path); + return ret; + } + *last_slash = '\0'; + ret = rm_empty_dirs(tmp_path, first_dir); + if (ret != 0) { + fprintf(stderr, "Could not remove directories between %s and %s",first_dir, tmp_path ); + } + } + // creation of subdir failed + return ret; +} + +// read_block_dev_serial: Read the serial ID of a block device from the respective sysfs +// entry. +// +// Arguments: +// 1. device_name: The device name +// 2. serial: The buffer to hold the serial ID that was found +// 2. size: The max size of the buffer +// +// Return value: +// If the device exists, then 0 is returned. +// If the deivce doe snot exist 1 is returned. +// In all other cases or errors -1 is returned. +int read_block_dev_serial(const char *device_name, char *serial, const size_t size) { + char path[PATH_MAX]; + FILE *fp; + int ret = 0; + + ret = snprintf(path, sizeof(path), "/sys/block/%s/serial", device_name); + if (ret < 0 || (size_t)ret > sizeof(path)) { + fprintf(stderr, "Could not create sysfs path for %s\n", device_name); + return -1; + } + + fp = fopen(path, "r"); + if (!fp) { + if (errno == ENOENT) { + return 1; + } + perror("fopen"); + return -1; + } + + if (fgets(serial, size, fp) == NULL) { + fclose(fp); + return -1; + } + + // Remove trailing whitespace + serial[strcspn(serial, "\n\r \t")] = '\0'; + fclose(fp); + return 0; +} + +// find_vblock_device_by_order: Returns the nth virtio block device (vd*) if it +// exists. The order is based on the conventional naming of virtio block devices +// in Linux where usually the first attached is vda, second vdb etc. +// +// Arguments: +// 1. n: The order of the virtio blockd evice to retrun. +// 2. device_path: The buffer that will store the path to the block device. +// +// Return value: +// On success 0 is returned and device_path parameter will hold the path +// the the device with the specific ID. +// Otherwise -1 is returned. +int find_vblock_device_by_order(const uint32_t n, char *device_path) { + // TODO: Add support for more than 26 devices. + char suffix = 'a' + (n % 26); + char device_name[] = "/dev/vda"; + int ret = 0; + + device_name[7] = suffix; + ret = access(device_name, F_OK); + if (ret) + return -1; + + snprintf(device_path, PATH_MAX, "%s", device_name); + return 0; +} + +// find_vblock_device_by_serial: Search all virtio block devices (vd[a-z]) to find the +// one with a specific serial ID. +// +// Arguments: +// 1. target_serial: The serial ID to search for in the devices +// 2. device_path: The buffer that will store the path to the block device +// +// Return value: +// On success 0 is returned and device_path parameter will hold the path +// the the device with the specific ID. +// Otherwise -1 is returned. +int find_vblock_device_by_serial(const char *target_serial, char *device_path) { + char suffix = 0; + char serial[SERIAL_MAX_SZ]; + char device_name[] = "vda"; + + for (suffix = 'a'; suffix <= 'z'; suffix++) { + int ret = 0; + + device_name[2] = suffix; + ret = read_block_dev_serial(device_name, serial, sizeof(serial)); + if (ret < 0) { + fprintf(stderr, "Error getting serial id of %s\n", device_name); + continue; + } else if (ret > 0) { + // The device does not exist. Move to the next one. + continue; + } + if (strcmp(serial, target_serial) == 0) { + snprintf(device_path, PATH_MAX, "/dev/%s", device_name); + return 0; + } + } + + return -1; +} + +// mount_block_vols: Mounts all block devices using their info from the +// block_config parameter. +// +// Arguments: +// 1. vols: An array of struct block_config with information to mount +// block volumes +// +// Return value: +// On success 0 is returned. +// Otherwise 1 is returned. +int mount_block_vols(struct block_config **vols) { + struct block_config **iter_bc = NULL; + char first_new_dir[PATH_MAX] = { 0 }; + uint32_t blk_count = 0; + + if (vols == NULL) { + DEBUG_PRINT("No block volumes to mount, nothing to do\n"); + return 0; + } + + for (iter_bc = vols; *iter_bc != NULL; iter_bc++) { + struct block_config *tmp_bc = *iter_bc; + char block_dev[PATH_MAX] = { 0 }; + int ret = 0; + + blk_count++; + first_new_dir[0] = '\0'; + DEBUG_PRINTF("Searching block device with serial ID %s\n", tmp_bc->id); + if (strlen(tmp_bc->id) > 2 && tmp_bc->id[0] == 'F' && tmp_bc->id[1] == 'C') { + ret = find_vblock_device_by_order(blk_count, block_dev); + } else { + ret = find_vblock_device_by_serial(tmp_bc->id, block_dev); + } + if (ret) { + fprintf(stderr, "Could not find any virtio block device with serial ID %s\n", tmp_bc->id); + continue; + } + DEBUG_PRINTF("Found device %s\n", block_dev); + DEBUG_PRINTF("Setup the mountpoint %s\n", tmp_bc->mountpoint); + ret = mkdir_all(tmp_bc->mountpoint, 0755, first_new_dir); + if (ret != 0 ) { + fprintf(stderr, "Failed to create %s\n",tmp_bc->mountpoint); + continue; + } + DEBUG_PRINT("Mount device as ext4\n"); + // TODO: SUpport more filesystem types + ret = mount(block_dev, tmp_bc->mountpoint, "ext4", 0, ""); + if (ret != 0) { + perror("mount"); + // Remove previously created directories. + // NOTE: In case of an error we just print a warning + // We might want to revisit this in the future. + ret = rm_empty_dirs(tmp_bc->mountpoint, first_new_dir); + if (ret < 0) { + fprintf(stderr, "WARNING: Could not remove %s and its subdirs\n", tmp_bc->mountpoint); + } + } + } + + return 0; +} + int child_func(char *argv[]) { char *config_file = NULL; char *config_buf = NULL; @@ -873,6 +1397,11 @@ int child_func(char *argv[]) { app_config = get_config_from_file(config_file, &config_buf); } if (app_config) { + ret = mount_block_vols(app_config->blk_conf); + if (ret != 0) { + fprintf(stderr, "Failed to mount block volumes\n"); + goto child_func_free; + } ret = setup_exec_env(app_config->pr_conf); if (ret != 0) { fprintf(stderr, "Failed to set up the process execution environment\n"); @@ -1115,6 +1644,186 @@ int set_default_route() { return ret; } +// is_block_fs: Checks if the parameter belongs to a list of known block-based +// filesystems. +// +// Arguments: +// 1. fs_type: The filesystem type to check +// +// Return value: +// If the filesystem type is a known block-based filesystem type then 1 is returned. +// Otherwise 0 is returned. +int is_block_fs(const char *fs_type) { + const char *block_types[] = { + "ext2", "ext3", "ext4", "xfs", "btrfs", "f2fs", + "jfs", "reiserfs", "nilfs2", "vfat", "ntfs", "exfat", + "hfs", "hfsplus", "ufs", "minix", "iso9660", "udf", + NULL + }; + int i = 0; + + for (i = 0; block_types[i] != NULL; i++) { + if (strcmp(fs_type, block_types[i]) == 0) { + return 1; + } + } + return 0; +} + +// is_network_fs: Checks if the parameter belongs to a list of known network-based +// filesystems. +// +// Arguments: +// 1. fs_type: The filesystem type to check +// +// Return value: +// If the filesystem type is a known network-based filesystem type then 1 is returned. +// Otherwise 0 is returned. +int is_network_fs(const char *fs_type) { + const char *network_types[] = { + "nfs", "nfs4", "cifs", "smb", "smbfs", + "ncpfs", "coda", "afs", "9p", + "glusterfs", "lustre", "ceph", "ocfs2", + NULL + }; + + for (int i = 0; network_types[i] != NULL; i++) { + if (strcmp(fs_type, network_types[i]) == 0) { + return 1; + } + } + return 0; +} + +// is_cloud_storage_fs: Checks if the parameter belongs to a list of known cloud-based +// filesystems. +// +// Arguments: +// 1. fs_type: The filesystem type to check +// +// Return value: +// If the filesystem type is a known cloud-based filesystem type then 1 is returned. +// Otherwise 0 is returned. +int is_cloud_storage_fs(const char *fs_type) { + const char *cloud_types[] = { + "fuse.s3fs", "fuse.goofys", "fuse.s3backer", "fuse.gcsfuse", + "fuse.blobfuse", "fuse.rclone", "fuse.juicefs", "fuse.sshfs", + "fuse.curlftpfs", "fuse.davfs2", "fuse.httpfs", "fuse.s3ql", + "fuse.ossfs", "fuse.cosfs", "fuse.obsfs", "iscsi", "seaweedfs", + "minio", + NULL + }; + + for (int i = 0; cloud_types[i] != NULL; i++) { + if (strcmp(fs_type, cloud_types[i]) == 0) { + return 1; + } + } + + return 0; +} + +// skip_n_words: Returns a pointer after the first n words of a string +// +// Arguments: +// 1. str: The string +// 2. n: The number fo words to skip +// +// Return value: +// On success it returns a pointer right after the first n words inside the string str +// Otherwise str is returned. +char *skip_n_words(const char *str, size_t n) { + const char *c = str; + while (n > 0 && *c) { + // Skip multiple spaces + while (isspace((unsigned char)*c)) + c++; + // Walk the word till space or end of string + while (*c && !isspace((unsigned char)*c)) + c++; + n--; + } + + // Move to the beginning of the next word + while (isspace((unsigned char)*c)) + c++; + if (*c == 0) { + return (char *)str; + } + + return (char *)c; +} + +// unmount_external: Unmounts all the external filesstem mounts found in +// /proc/self/mountinfo. External means all the known block, network and cloud storage +// based filesystems. +// +// Arguments: +// +// Return value: +void unmount_external() { + FILE *mount_info_f = NULL; + char line[1024] = { 0 }; + + mount_info_f = fopen("/proc/self/mountinfo", "r"); + if (!mount_info_f) { + perror("Error opening /proc/self/mountinfo"); + return; + } + + while (fgets(line, sizeof(line), mount_info_f)) { + char *tmp = NULL; + char *mount_point = NULL; + char *mount_type = NULL; + + mount_point = skip_n_words(line, 4); + if (mount_point == line) { + fprintf(stderr, "Malformed line in mountinfo. Could not reach mountpoint: %s\n", line); + continue; + } + tmp = strchr(mount_point, ' '); + if (!tmp) { + fprintf(stderr, "Malformed line in mountinfo. Could not get mountpoint: %s\n", line); + continue; + } + *tmp = '\0'; + tmp++; + DEBUG_PRINTF("Found mountpoint %s\n", mount_point); + // SKip rootfs because we can not unmount it easily. + // Also, the rootfs will be be based on the container's image + // and hence even if somehting goes wrong, a new instance of it + // will get created for another container. Therefore, it will not + // get reused. + if (strcmp(mount_point, "/") == 0) + continue; + mount_type = strstr(tmp, " - "); + if (!mount_type) { + fprintf(stderr, "Malformed line in mountinfo. Could not reach mount type: %s%s\n", line, tmp); + continue; + } + mount_type += 3; + tmp = strchr(mount_type, ' '); + if (!tmp) { + fprintf(stderr, "Malformed line in mountinfo. Could not get mount type: %s%s\n", line, mount_type - 3); + continue; + } + *tmp = '\0'; + DEBUG_PRINTF("Found mount type %s\n", mount_type); + if (is_block_fs(mount_type) || + is_network_fs(mount_type) || + is_cloud_storage_fs(mount_type)) { + int ret = 0; + DEBUG_PRINTF("Trying to unmount %s\n", mount_point); + ret = umount2(mount_point, MNT_FORCE); + if (ret) { + perror("umount"); + } else { + DEBUG_PRINTF("Successful unmount of %s\n", mount_point); + } + } + } +} + int main(int argc, char *argv[]) { pid_t app_pid; int ret = 0; @@ -1159,6 +1868,7 @@ int main(int argc, char *argv[]) { DEBUG_PRINT("Exiting, will reboot in order to shutdown\n"); sync(); + unmount_external(); syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART, NULL); }