| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright 2018 Google LLC | |
| */ | |
| #include <dirent.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <poll.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/mount.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <openssl/sha.h> | |
| #include <openssl/md5.h> | |
| #include "utils.h" | |
| #ifndef __S_IFREG | |
| #define __S_IFREG S_IFREG | |
| #endif | |
| unsigned int rnd(unsigned int max, unsigned int *seed) | |
| { | |
| return rand_r(seed) * ((uint64_t)max + 1) / RAND_MAX; | |
| } | |
| int remove_dir(const char *dir) | |
| { | |
| int err = rmdir(dir); | |
| if (err && errno == ENOTEMPTY) { | |
| err = delete_dir_tree(dir); | |
| if (err) | |
| return err; | |
| return 0; | |
| } | |
| if (err && errno != ENOENT) | |
| return -errno; | |
| return 0; | |
| } | |
| int drop_caches(void) | |
| { | |
| int drop_caches = | |
| open("/proc/sys/vm/drop_caches", O_WRONLY | O_CLOEXEC); | |
| int i; | |
| if (drop_caches == -1) | |
| return -errno; | |
| i = write(drop_caches, "3", 1); | |
| close(drop_caches); | |
| if (i != 1) | |
| return -errno; | |
| return 0; | |
| } | |
| int mount_fs(const char *mount_dir, const char *backing_dir, | |
| int read_timeout_ms) | |
| { | |
| static const char fs_name[] = INCFS_NAME; | |
| char mount_options[512]; | |
| int result; | |
| snprintf(mount_options, ARRAY_SIZE(mount_options), | |
| "read_timeout_ms=%u", | |
| read_timeout_ms); | |
| result = mount(backing_dir, mount_dir, fs_name, 0, mount_options); | |
| if (result != 0) | |
| perror("Error mounting fs."); | |
| return result; | |
| } | |
| int umount_fs(const char *mount_dir) | |
| { | |
| int result; | |
| result = umount(mount_dir); | |
| if (result != 0) | |
| perror("Error unmounting fs."); | |
| return result; | |
| } | |
| int mount_fs_opt(const char *mount_dir, const char *backing_dir, | |
| const char *opt, bool remount) | |
| { | |
| static const char fs_name[] = INCFS_NAME; | |
| int result; | |
| result = mount(backing_dir, mount_dir, fs_name, | |
| remount ? MS_REMOUNT : 0, opt); | |
| if (result != 0) | |
| perror("Error mounting fs."); | |
| return result; | |
| } | |
| struct hash_section { | |
| uint32_t algorithm; | |
| uint8_t log2_blocksize; | |
| uint32_t salt_size; | |
| /* no salt */ | |
| uint32_t hash_size; | |
| uint8_t hash[SHA256_DIGEST_SIZE]; | |
| } __packed; | |
| struct signature_blob { | |
| uint32_t version; | |
| uint32_t hash_section_size; | |
| struct hash_section hash_section; | |
| uint32_t signing_section_size; | |
| uint8_t signing_section[]; | |
| } __packed; | |
| size_t format_signature(void **buf, const char *root_hash, const char *add_data) | |
| { | |
| size_t size = sizeof(struct signature_blob) + strlen(add_data) + 1; | |
| struct signature_blob *sb = malloc(size); | |
| if (!sb) | |
| return 0; | |
| *sb = (struct signature_blob){ | |
| .version = INCFS_SIGNATURE_VERSION, | |
| .hash_section_size = sizeof(struct hash_section), | |
| .hash_section = | |
| (struct hash_section){ | |
| .algorithm = INCFS_HASH_TREE_SHA256, | |
| .log2_blocksize = 12, | |
| .salt_size = 0, | |
| .hash_size = SHA256_DIGEST_SIZE, | |
| }, | |
| .signing_section_size = strlen(add_data) + 1, | |
| }; | |
| memcpy(sb->hash_section.hash, root_hash, SHA256_DIGEST_SIZE); | |
| memcpy((char *)sb->signing_section, add_data, strlen(add_data) + 1); | |
| *buf = sb; | |
| return size; | |
| } | |
| int crypto_emit_file(int fd, const char *dir, const char *filename, | |
| incfs_uuid_t *id_out, size_t size, const char *root_hash, | |
| const char *add_data) | |
| { | |
| int mode = __S_IFREG | 0555; | |
| void *signature; | |
| int error = 0; | |
| struct incfs_new_file_args args = { | |
| .size = size, | |
| .mode = mode, | |
| .file_name = ptr_to_u64(filename), | |
| .directory_path = ptr_to_u64(dir), | |
| .file_attr = 0, | |
| .file_attr_len = 0 | |
| }; | |
| args.signature_size = format_signature(&signature, root_hash, add_data); | |
| args.signature_info = ptr_to_u64(signature); | |
| md5(filename, strlen(filename), (char *)args.file_id.bytes); | |
| if (ioctl(fd, INCFS_IOC_CREATE_FILE, &args) != 0) { | |
| error = -errno; | |
| goto out; | |
| } | |
| *id_out = args.file_id; | |
| out: | |
| free(signature); | |
| return error; | |
| } | |
| int emit_file(int fd, const char *dir, const char *filename, | |
| incfs_uuid_t *id_out, size_t size, const char *attr) | |
| { | |
| int mode = __S_IFREG | 0555; | |
| struct incfs_new_file_args args = { .size = size, | |
| .mode = mode, | |
| .file_name = ptr_to_u64(filename), | |
| .directory_path = ptr_to_u64(dir), | |
| .signature_info = ptr_to_u64(NULL), | |
| .signature_size = 0, | |
| .file_attr = ptr_to_u64(attr), | |
| .file_attr_len = | |
| attr ? strlen(attr) : 0 }; | |
| md5(filename, strlen(filename), (char *)args.file_id.bytes); | |
| if (ioctl(fd, INCFS_IOC_CREATE_FILE, &args) != 0) | |
| return -errno; | |
| *id_out = args.file_id; | |
| return 0; | |
| } | |
| int get_file_bmap(int cmd_fd, int ino, unsigned char *buf, int buf_size) | |
| { | |
| return 0; | |
| } | |
| int get_file_signature(int fd, unsigned char *buf, int buf_size) | |
| { | |
| struct incfs_get_file_sig_args args = { | |
| .file_signature = ptr_to_u64(buf), | |
| .file_signature_buf_size = buf_size | |
| }; | |
| if (ioctl(fd, INCFS_IOC_READ_FILE_SIGNATURE, &args) == 0) | |
| return args.file_signature_len_out; | |
| return -errno; | |
| } | |
| loff_t get_file_size(const char *name) | |
| { | |
| struct stat st; | |
| if (stat(name, &st) == 0) | |
| return st.st_size; | |
| return -ENOENT; | |
| } | |
| int open_commands_file(const char *mount_dir) | |
| { | |
| char cmd_file[255]; | |
| int cmd_fd; | |
| snprintf(cmd_file, ARRAY_SIZE(cmd_file), | |
| "%s/%s", mount_dir, INCFS_PENDING_READS_FILENAME); | |
| cmd_fd = open(cmd_file, O_RDONLY | O_CLOEXEC); | |
| if (cmd_fd < 0) | |
| perror("Can't open commands file"); | |
| return cmd_fd; | |
| } | |
| int open_log_file(const char *mount_dir) | |
| { | |
| char file[255]; | |
| int fd; | |
| snprintf(file, ARRAY_SIZE(file), "%s/.log", mount_dir); | |
| fd = open(file, O_RDWR | O_CLOEXEC); | |
| if (fd < 0) | |
| perror("Can't open log file"); | |
| return fd; | |
| } | |
| int open_blocks_written_file(const char *mount_dir) | |
| { | |
| char file[255]; | |
| int fd; | |
| snprintf(file, ARRAY_SIZE(file), | |
| "%s/%s", mount_dir, INCFS_BLOCKS_WRITTEN_FILENAME); | |
| fd = open(file, O_RDONLY | O_CLOEXEC); | |
| if (fd < 0) | |
| perror("Can't open blocks_written file"); | |
| return fd; | |
| } | |
| int wait_for_pending_reads(int fd, int timeout_ms, | |
| struct incfs_pending_read_info *prs, int prs_count) | |
| { | |
| ssize_t read_res = 0; | |
| if (timeout_ms > 0) { | |
| int poll_res = 0; | |
| struct pollfd pollfd = { | |
| .fd = fd, | |
| .events = POLLIN | |
| }; | |
| poll_res = poll(&pollfd, 1, timeout_ms); | |
| if (poll_res < 0) | |
| return -errno; | |
| if (poll_res == 0) | |
| return 0; | |
| if (!(pollfd.revents | POLLIN)) | |
| return 0; | |
| } | |
| read_res = read(fd, prs, prs_count * sizeof(*prs)); | |
| if (read_res < 0) | |
| return -errno; | |
| return read_res / sizeof(*prs); | |
| } | |
| int wait_for_pending_reads2(int fd, int timeout_ms, | |
| struct incfs_pending_read_info2 *prs, int prs_count) | |
| { | |
| ssize_t read_res = 0; | |
| if (timeout_ms > 0) { | |
| int poll_res = 0; | |
| struct pollfd pollfd = { | |
| .fd = fd, | |
| .events = POLLIN | |
| }; | |
| poll_res = poll(&pollfd, 1, timeout_ms); | |
| if (poll_res < 0) | |
| return -errno; | |
| if (poll_res == 0) | |
| return 0; | |
| if (!(pollfd.revents | POLLIN)) | |
| return 0; | |
| } | |
| read_res = read(fd, prs, prs_count * sizeof(*prs)); | |
| if (read_res < 0) | |
| return -errno; | |
| return read_res / sizeof(*prs); | |
| } | |
| char *concat_file_name(const char *dir, const char *file) | |
| { | |
| char full_name[FILENAME_MAX] = ""; | |
| if (snprintf(full_name, ARRAY_SIZE(full_name), "%s/%s", dir, file) < 0) | |
| return NULL; | |
| return strdup(full_name); | |
| } | |
| int delete_dir_tree(const char *dir_path) | |
| { | |
| DIR *dir = NULL; | |
| struct dirent *dp; | |
| int result = 0; | |
| dir = opendir(dir_path); | |
| if (!dir) { | |
| result = -errno; | |
| goto out; | |
| } | |
| while ((dp = readdir(dir))) { | |
| char *full_path; | |
| if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) | |
| continue; | |
| full_path = concat_file_name(dir_path, dp->d_name); | |
| if (dp->d_type == DT_DIR) | |
| result = delete_dir_tree(full_path); | |
| else | |
| result = unlink(full_path); | |
| free(full_path); | |
| if (result) | |
| goto out; | |
| } | |
| out: | |
| if (dir) | |
| closedir(dir); | |
| if (!result) | |
| rmdir(dir_path); | |
| return result; | |
| } | |
| void sha256(const char *data, size_t dsize, char *hash) | |
| { | |
| SHA256_CTX ctx; | |
| SHA256_Init(&ctx); | |
| SHA256_Update(&ctx, data, dsize); | |
| SHA256_Final((unsigned char *)hash, &ctx); | |
| } | |
| void md5(const char *data, size_t dsize, char *hash) | |
| { | |
| MD5_CTX ctx; | |
| MD5_Init(&ctx); | |
| MD5_Update(&ctx, data, dsize); | |
| MD5_Final((unsigned char *)hash, &ctx); | |
| } |