storageproxyd: Add arguments for storage mapping and max file

Background:
* -f = Allows mapping files in the format `-f file:backing_file`.  This
  can be used for mapping secure storage files like `0` and `persist/0`
  to block devices.  Storageproxyd will handle creating the appropriate
  symlinks in the root datapath
* -m = Allows specifying the the max size constraint for file backed storages.
  The constraint is chosen by giving a file, this allows for passing a
  block device for which a max file size can be queried.  File based
  storages will be constrained to that size as well.

Bug: 324989972
Test: File sizes are restricted as specified, and mappings are created
Change-Id: I8ff550afafbd372288daa9e27c4db3451948b25d
Signed-off-by: Donnie Pollitz <donpollitz@google.com>
diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c
index 67e935e..6cb72d5 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -41,9 +41,13 @@
 static const char* trusty_devname;
 static const char* rpmb_devname;
 static const char* ss_srv_name = STORAGE_DISK_PROXY_PORT;
+static const char* max_file_size_from;
 
 static enum dev_type dev_type = MMC_RPMB;
 
+/* List head for storage mapping, elements added at init, and never removed */
+static struct storage_mapping_node* storage_mapping_head;
+
 static enum dev_type parse_dev_type(const char* dev_type_name) {
     if (!strcmp(dev_type_name, "mmc")) {
         return MMC_RPMB;
@@ -58,17 +62,61 @@
     }
 }
 
-static const char* _sopts = "hp:d:r:t:";
+static int parse_and_append_file_mapping(const char* file_mapping) {
+    if (file_mapping == NULL) {
+        ALOGE("Provided file mapping is null\n");
+        return -1;
+    }
+    char* file_mapping_dup = strdup(file_mapping);
+    if (file_mapping_dup == NULL) {
+        ALOGE("Couldn't duplicate string: %s\n", file_mapping);
+        return -1;
+    }
+    const char* file_name = strtok(file_mapping_dup, ":");
+    if (file_name == NULL) {
+        ALOGE("No file name found\n");
+        return -1;
+    }
+    const char* backing_storage = strtok(NULL, ":");
+    if (backing_storage == NULL) {
+        ALOGE("No backing storage found\n");
+        return -1;
+    }
+
+    struct storage_mapping_node* new_node = malloc(sizeof(struct storage_mapping_node));
+    if (new_node == NULL) {
+        ALOGE("Couldn't allocate additional storage_mapping_node\n");
+        return -1;
+    }
+    *new_node = (struct storage_mapping_node){.file_name = file_name,
+                                              .backing_storage = backing_storage,
+                                              .next = storage_mapping_head,
+                                              .fd = -1};
+    storage_mapping_head = new_node;
+    return 0;
+}
+
+static const char* _sopts = "hp:d:r:t:m:f:";
 static const struct option _lopts[] = {{"help", no_argument, NULL, 'h'},
                                        {"trusty_dev", required_argument, NULL, 'd'},
                                        {"data_path", required_argument, NULL, 'p'},
                                        {"rpmb_dev", required_argument, NULL, 'r'},
                                        {"dev_type", required_argument, NULL, 't'},
+                                       {"max_file_size_from", required_argument, NULL, 'm'},
+                                       {"file_storage_mapping", required_argument, NULL, 'f'},
                                        {0, 0, 0, 0}};
 
 static void show_usage_and_exit(int code) {
-    ALOGE("usage: storageproxyd -d <trusty_dev> -p <data_path> -r <rpmb_dev> -t <dev_type>\n");
+    ALOGE("usage: storageproxyd -d <trusty_dev> -p <data_path> -r <rpmb_dev> -t <dev_type>  [-m "
+          "<file>] [-f <file>:<mapping>]\n");
     ALOGE("Available dev types: mmc, virt\n");
+    ALOGE("-f = Maps secure storage files like `0` and `persist/0`\n"
+          "to block devices.  Storageproxyd will handle creating the\n"
+          "appropriate symlinks in the root datapath.\n");
+    ALOGE("-m = Specifies the max size constraint for file backed storages.\n"
+          "The constraint is chosen by giving a file, this allows for passing a\n"
+          "block device for which a max file size can be queried.  File based\n"
+          "storages will be constrained to that size as well.\n");
     exit(code);
 }
 
@@ -187,6 +235,7 @@
 static void parse_args(int argc, char* argv[]) {
     int opt;
     int oidx = 0;
+    int rc = 0;
 
     while ((opt = getopt_long(argc, argv, _sopts, _lopts, &oidx)) != -1) {
         switch (opt) {
@@ -210,6 +259,18 @@
                 }
                 break;
 
+            case 'f':
+                rc = parse_and_append_file_mapping(optarg);
+                if (rc < 0) {
+                    ALOGE("Failed to parse file mapping: %s\n", optarg);
+                    show_usage_and_exit(EXIT_FAILURE);
+                }
+                break;
+
+            case 'm':
+                max_file_size_from = strdup(optarg);
+                break;
+
             default:
                 ALOGE("unrecognized option (%c):\n", opt);
                 show_usage_and_exit(EXIT_FAILURE);
@@ -225,6 +286,12 @@
     ALOGI("storage data root: %s\n", ss_data_root);
     ALOGI("trusty dev: %s\n", trusty_devname);
     ALOGI("rpmb dev: %s\n", rpmb_devname);
+    ALOGI("File Mappings: \n");
+    const struct storage_mapping_node* curr = storage_mapping_head;
+    for (; curr != NULL; curr = curr->next) {
+        ALOGI("\t%s -> %s\n", curr->file_name, curr->backing_storage);
+    }
+    ALOGI("max file size from: %s\n", max_file_size_from ? max_file_size_from : "(unset)");
 }
 
 int main(int argc, char* argv[]) {
@@ -252,7 +319,7 @@
     ABinderProcess_startThreadPool();
 
     /* initialize secure storage directory */
-    rc = storage_init(ss_data_root);
+    rc = storage_init(ss_data_root, storage_mapping_head, max_file_size_from);
     if (rc < 0) return EXIT_FAILURE;
 
     /* open rpmb device */
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index 8c8edb7..1dab93d 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <assert.h>
 #include <cutils/properties.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -39,16 +40,20 @@
 #define ALTERNATE_DATA_DIR "alternate/"
 
 /* Maximum file size for filesystem backed storage (i.e. not block dev backed storage) */
-#define MAX_FILE_SIZE (0x10000000000)
+static size_t max_file_size = 0x10000000000;
 
 enum sync_state {
     SS_UNUSED = -1,
-    SS_CLEAN =  0,
-    SS_DIRTY =  1,
+    SS_CLEAN = 0,
+    SS_DIRTY = 1,
+    SS_CLEAN_NEED_SYMLINK = 2,
 };
 
 static const char *ssdir_name;
 
+/* List head for storage mapping, elements added at init, and never removed */
+static struct storage_mapping_node* storage_mapping_head;
+
 /*
  * Property set to 1 after we have opened a file under ssdir_name. The backing
  * files for both TD and TDP are currently located under /data/vendor/ss and can
@@ -75,24 +80,103 @@
    uint8_t data[MAX_READ_SIZE];
 }  read_rsp;
 
-static uint32_t insert_fd(int open_flags, int fd)
-{
+static uint32_t insert_fd(int open_flags, int fd, struct storage_mapping_node* node) {
     uint32_t handle = fd;
 
     if (handle < FD_TBL_SIZE) {
-            fd_state[fd] = SS_CLEAN; /* fd clean */
-            if (open_flags & O_TRUNC) {
-                fd_state[fd] = SS_DIRTY;  /* set fd dirty */
-            }
+        fd_state[fd] = SS_CLEAN; /* fd clean */
+        if (open_flags & O_TRUNC) {
+            assert(node == NULL);
+            fd_state[fd] = SS_DIRTY; /* set fd dirty */
+        }
+
+        if (node != NULL) {
+            fd_state[fd] = SS_CLEAN_NEED_SYMLINK;
+        }
     } else {
             ALOGW("%s: untracked fd %u\n", __func__, fd);
             if (open_flags & (O_TRUNC | O_CREAT)) {
                 fs_state = SS_DIRTY;
             }
     }
+
+    if (node != NULL) {
+        node->fd = fd;
+    }
+
     return handle;
 }
 
+static void clear_fd_symlink_status(uint32_t handle, struct storage_mapping_node* entry) {
+    /* Always clear FD, in case fd is not in FD_TBL */
+    entry->fd = -1;
+
+    if (handle >= FD_TBL_SIZE) {
+        ALOGE("%s: untracked fd=%u\n", __func__, handle);
+        return;
+    }
+
+    if (fd_state[handle] == SS_CLEAN_NEED_SYMLINK) {
+        fd_state[handle] = SS_CLEAN;
+    }
+}
+
+static struct storage_mapping_node* get_pending_symlink_mapping(uint32_t handle) {
+    /* Fast lookup failure, is it in FD TBL */
+    if (handle < FD_TBL_SIZE && fd_state[handle] != SS_CLEAN_NEED_SYMLINK) {
+        return NULL;
+    }
+
+    /* Go find our mapping */
+    struct storage_mapping_node* curr = storage_mapping_head;
+    for (; curr != NULL; curr = curr->next) {
+        if (curr->fd == handle) {
+            return curr;
+        }
+    }
+
+    /* Safety check: state inconsistent if we get here with handle inside table range */
+    assert(handle >= FD_TBL_SIZE);
+
+    return NULL;
+};
+
+static int possibly_symlink_and_clear_mapping(uint32_t handle) {
+    struct storage_mapping_node* entry = get_pending_symlink_mapping(handle);
+    if (entry == NULL) {
+        /* No mappings pending */
+        return 0;
+    }
+
+    /* Create full path */
+    char* path = NULL;
+    int rc = asprintf(&path, "%s/%s", ssdir_name, entry->file_name);
+    if (rc < 0) {
+        ALOGE("%s: asprintf failed\n", __func__);
+        return -1;
+    }
+
+    /* Try and setup the symlinking */
+    ALOGI("Creating symlink %s->%s\n", path, entry->backing_storage);
+    rc = symlink(entry->backing_storage, path);
+    if (rc < 0) {
+        ALOGE("%s: error symlinking %s->%s (%s)\n", __func__, path, entry->backing_storage,
+              strerror(errno));
+        free(path);
+        return rc;
+    }
+    free(path);
+
+    clear_fd_symlink_status(handle, entry);
+
+    return rc;
+}
+
+static bool is_pending_symlink(uint32_t handle) {
+    struct storage_mapping_node* entry = get_pending_symlink_mapping(handle);
+    return entry != NULL;
+}
+
 static int lookup_fd(uint32_t handle, bool dirty)
 {
     if (dirty) {
@@ -107,6 +191,12 @@
 
 static int remove_fd(uint32_t handle)
 {
+    /* Cleanup fd in symlink mapping if it exists */
+    struct storage_mapping_node* entry = get_pending_symlink_mapping(handle);
+    if (entry != NULL) {
+        entry->fd = -1;
+    }
+
     if (handle < FD_TBL_SIZE) {
         fd_state[handle] = SS_UNUSED; /* set to uninstalled */
     }
@@ -247,11 +337,73 @@
     watch_progress(watcher, "done syncing parent");
 }
 
+static struct storage_mapping_node* get_storage_mapping_entry(const char* source) {
+    struct storage_mapping_node* curr = storage_mapping_head;
+    for (; curr != NULL; curr = curr->next) {
+        if (!strcmp(source, curr->file_name)) {
+            ALOGI("Found backing file %s for %s\n", curr->backing_storage, source);
+            return curr;
+        }
+    }
+    return NULL;
+}
+
+static bool is_backing_storage_mapped(const char* source) {
+    const struct storage_mapping_node* curr = storage_mapping_head;
+    for (; curr != NULL; curr = curr->next) {
+        if (!strcmp(source, curr->backing_storage)) {
+            ALOGI("Backed storage mapping exists for %s\n", curr->backing_storage);
+            return true;
+        }
+    }
+    return false;
+}
+
+/* Attempts to open a backed file, if mapped, without creating the symlink. Symlink will be created
+ * later on the first write.  This allows us to continue reporting zero read sizes until the first
+ * write. */
+static int open_possibly_mapped_file(const char* short_path, const char* full_path, int open_flags,
+                                     struct storage_mapping_node** entry) {
+    /* See if mapping exists, report upstream if there is no mapping. */
+    struct storage_mapping_node* mapping_entry = get_storage_mapping_entry(short_path);
+    if (mapping_entry == NULL) {
+        return TEMP_FAILURE_RETRY(open(full_path, open_flags, S_IRUSR | S_IWUSR));
+    }
+
+    /* Check for existence of root path, we don't allow mappings during early boot */
+    struct stat buf = {0};
+    if (stat(ssdir_name, &buf) != 0) {
+        ALOGW("Root path not accessible yet, refuse to open mappings for now.\n");
+        return -1;
+    }
+
+    /* We don't support exclusive opening of mapped files */
+    if (open_flags & O_EXCL) {
+        ALOGE("Requesting exclusive open on backed storage isn't supported: %s\n", full_path);
+        return -1;
+    }
+
+    /* Try and open mapping file */
+    open_flags &= ~(O_CREAT | O_EXCL);
+    ALOGI("%s Attempting to open mapped file: %s\n", __func__, mapping_entry->backing_storage);
+    int fd =
+            TEMP_FAILURE_RETRY(open(mapping_entry->backing_storage, open_flags, S_IRUSR | S_IWUSR));
+    if (fd < 0) {
+        ALOGE("%s Failed to open mapping file: %s\n", __func__, mapping_entry->backing_storage);
+        return -1;
+    }
+
+    /* Let caller know which entry we used for opening */
+    *entry = mapping_entry;
+    return fd;
+}
+
 int storage_file_open(struct storage_msg* msg, const void* r, size_t req_len,
                       struct watcher* watcher) {
     char* path = NULL;
     const struct storage_file_open_req *req = r;
     struct storage_file_open_resp resp = {0};
+    struct storage_mapping_node* mapping_entry = NULL;
 
     if (req_len < sizeof(*req)) {
         ALOGE("%s: invalid request length (%zd < %zd)\n",
@@ -321,14 +473,18 @@
         if (req->flags & STORAGE_FILE_OPEN_CREATE_EXCLUSIVE) {
             /* create exclusive */
             open_flags |= O_CREAT | O_EXCL;
-            rc = TEMP_FAILURE_RETRY(open(path, open_flags, S_IRUSR | S_IWUSR));
+
+            /* Look for and attempt opening a mapping, else just do normal open. */
+            rc = open_possibly_mapped_file(req->name, path, open_flags, &mapping_entry);
         } else {
             /* try open first */
             rc = TEMP_FAILURE_RETRY(open(path, open_flags, S_IRUSR | S_IWUSR));
             if (rc == -1 && errno == ENOENT) {
                 /* then try open with O_CREATE */
                 open_flags |= O_CREAT;
-                rc = TEMP_FAILURE_RETRY(open(path, open_flags, S_IRUSR | S_IWUSR));
+
+                /* Look for and attempt opening a mapping, else just do normal open. */
+                rc = open_possibly_mapped_file(req->name, path, open_flags, &mapping_entry);
             }
 
         }
@@ -356,7 +512,7 @@
 
     /* at this point rc contains storage file fd */
     msg->result = STORAGE_NO_ERROR;
-    resp.handle = insert_fd(open_flags, rc);
+    resp.handle = insert_fd(open_flags, rc, mapping_entry);
     ALOGV("%s: \"%s\": fd = %u: handle = %d\n",
           __func__, path, rc, resp.handle);
 
@@ -433,6 +589,14 @@
         goto err_response;
     }
 
+    /* Handle any delayed symlinking for this handle if any */
+    rc = possibly_symlink_and_clear_mapping(req->handle);
+    if (rc < 0) {
+        ALOGE("Failed to symlink storage\n");
+        msg->result = STORAGE_ERR_GENERIC;
+        goto err_response;
+    }
+
     int fd = lookup_fd(req->handle, true);
     watch_progress(watcher, "writing");
     if (write_with_retry(fd, &req->data[0], req_len - sizeof(*req),
@@ -479,6 +643,14 @@
         goto err_response;
     }
 
+    /* If this handle has a delayed symlink we should report 0 size reads until first write occurs
+     */
+    if (is_pending_symlink(req->handle)) {
+        ALOGI("Pending symlink: Forcing read result 0.\n");
+        msg->result = STORAGE_NO_ERROR;
+        return ipc_respond(msg, &read_rsp, sizeof(read_rsp.hdr));
+    }
+
     int fd = lookup_fd(req->handle, false);
     watch_progress(watcher, "reading");
     ssize_t read_res = read_with_retry(fd, read_rsp.hdr.data, req->size,
@@ -592,7 +764,7 @@
             goto err_response;
         }
     } else {
-        max_size = MAX_FILE_SIZE;
+        max_size = max_file_size;
     }
 
     resp.max_size = max_size;
@@ -603,17 +775,78 @@
     return ipc_respond(msg, NULL, 0);
 }
 
-int storage_init(const char *dirname)
-{
+int determine_max_file_size(const char* max_file_size_from) {
+    /* Use default if none passed in */
+    if (max_file_size_from == NULL) {
+        ALOGI("No max file source given, continuing to use default: 0x%lx\n", max_file_size);
+        return 0;
+    }
+
+    /* Check that max_file_size_from is part of our mapping list. */
+    if (!is_backing_storage_mapped(max_file_size_from)) {
+        ALOGE("%s: file doesn't match mapped storages (filename=%s)\n", __func__,
+              max_file_size_from);
+        return -1;
+    }
+
+    ALOGI("Using %s to determine max file size.\n", max_file_size_from);
+
+    /* Error if max file size source not found, possible misconfig. */
+    struct stat buf = {0};
+    int rc = stat(max_file_size_from, &buf);
+    if (rc < 0) {
+        ALOGE("%s: error stat'ing file (filename=%s): %s\n", __func__, max_file_size_from,
+              strerror(errno));
+        return -1;
+    }
+
+    /* Currently only support block device as max file size source */
+    if ((buf.st_mode & S_IFMT) != S_IFBLK) {
+        ALOGE("Unsupported max file size source type: %d\n", buf.st_mode);
+        return -1;
+    }
+
+    ALOGI("%s is a block device, determining block device size\n", max_file_size_from);
+    uint64_t max_size = 0;
+    int fd = TEMP_FAILURE_RETRY(open(max_file_size_from, O_RDONLY | O_NONBLOCK));
+    if (fd < 0) {
+        ALOGE("%s: failed to open backing file %s for ioctl: %s\n", __func__, max_file_size_from,
+              strerror(errno));
+        return -1;
+    }
+    rc = ioctl(fd, BLKGETSIZE64, &max_size);
+    if (rc < 0) {
+        ALOGE("%s: error calling ioctl on file (fd=%d): %s\n", __func__, fd, strerror(errno));
+        close(fd);
+        return -1;
+    }
+    close(fd);
+    max_file_size = max_size;
+
+    ALOGI("Using 0x%lx as max file size\n", max_file_size);
+    return 0;
+}
+
+int storage_init(const char* dirname, struct storage_mapping_node* mappings,
+                 const char* max_file_size_from) {
     /* If there is an active DSU image, use the alternate fs mode. */
     alternate_mode = is_gsi_running();
 
     fs_state = SS_CLEAN;
     for (uint i = 0; i < FD_TBL_SIZE; i++) {
-        fd_state[i] = SS_UNUSED;  /* uninstalled */
+        fd_state[i] = SS_UNUSED; /* uninstalled */
     }
 
     ssdir_name = dirname;
+
+    storage_mapping_head = mappings;
+
+    /* Set the max file size based on incoming configuration */
+    int rc = determine_max_file_size(max_file_size_from);
+    if (rc < 0) {
+        return rc;
+    }
+
     return 0;
 }
 
@@ -623,17 +856,17 @@
     watch_progress(watcher, "sync fd table");
     /* sync fd table and reset it to clean state first */
     for (uint fd = 0; fd < FD_TBL_SIZE; fd++) {
-         if (fd_state[fd] == SS_DIRTY) {
-             if (fs_state == SS_CLEAN) {
-                 /* need to sync individual fd */
-                 rc = fsync(fd);
-                 if (rc < 0) {
-                     ALOGE("fsync for fd=%d failed: %s\n", fd, strerror(errno));
-                     return rc;
-                 }
-             }
-             fd_state[fd] = SS_CLEAN; /* set to clean */
-         }
+        if (fd_state[fd] == SS_DIRTY) {
+            if (fs_state == SS_CLEAN) {
+                /* need to sync individual fd */
+                rc = fsync(fd);
+                if (rc < 0) {
+                    ALOGE("fsync for fd=%d failed: %s\n", fd, strerror(errno));
+                    return rc;
+                }
+            }
+            fd_state[fd] = SS_CLEAN; /* set to clean */
+        }
     }
 
     /* check if we need to sync all filesystems */
diff --git a/trusty/storage/proxy/storage.h b/trusty/storage/proxy/storage.h
index f29fdf2..6dbfe37 100644
--- a/trusty/storage/proxy/storage.h
+++ b/trusty/storage/proxy/storage.h
@@ -21,6 +21,14 @@
 /* Defined in watchdog.h */
 struct watcher;
 
+/* Is used for managing alternate backing storage, generally will be a block device. */
+struct storage_mapping_node {
+    struct storage_mapping_node* next;
+    const char* file_name;
+    const char* backing_storage;
+    int fd;
+};
+
 int storage_file_delete(struct storage_msg* msg, const void* req, size_t req_len,
                         struct watcher* watcher);
 
@@ -45,6 +53,7 @@
 int storage_file_get_max_size(struct storage_msg* msg, const void* req, size_t req_len,
                               struct watcher* watcher);
 
-int storage_init(const char* dirname);
+int storage_init(const char* dirname, struct storage_mapping_node* head,
+                 const char* max_file_size_from);
 
 int storage_sync_checkpoint(struct watcher* watcher);