/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "checkpoint_handling.h" #include "ipc.h" #include "log.h" #include "storage.h" #include "watchdog.h" #define FD_TBL_SIZE 64 #define MAX_READ_SIZE 4096 #define ALTERNATE_DATA_DIR "alternate/" /* Maximum file size for filesystem backed storage (i.e. not block dev backed storage) */ static uint64_t max_file_size = 0x10000000000; enum sync_state { SS_UNUSED = -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; #ifdef VENDOR_FS_READY_PROPERTY /* * Properties 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 * only be opened once userdata is mounted. This storageproxyd service is * restarted when userdata is available, which causes the Trusty storage service * to reconnect and attempt to open the backing files for TD and TDP. Once we * set this property, other users can expect that the Trusty storage service * ports will be available (although they may block if still being initialized), * and connections will not be reset after this point (assuming the * storageproxyd service stays running). * * fs_ready - secure storage is read-only (due to checkpointing after upgrade) * fs_ready_rw - secure storage is readable and writable */ #define FS_READY_PROPERTY "ro.vendor.trusty.storage.fs_ready" #define FS_READY_RW_PROPERTY "ro.vendor.trusty.storage.fs_ready_rw" /* has FS_READY_PROPERTY been set? */ static bool fs_ready_set = false; static bool fs_ready_rw_set = false; static bool property_set_helper(const char* prop) { int rc = property_set(prop, "1"); if (rc == 0) { ALOGI("Set property %s\n", prop); } else { ALOGE("Could not set property %s, rc: %d\n", prop, rc); } return rc == 0; } #endif // #ifdef VENDOR_FS_READY_PROPERTY static enum sync_state fs_state; static enum sync_state fd_state[FD_TBL_SIZE]; static bool alternate_mode; static struct { struct storage_file_read_resp hdr; uint8_t data[MAX_READ_SIZE]; } read_rsp; 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) { 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) { if (handle < FD_TBL_SIZE) { fd_state[handle] = SS_DIRTY; } else { fs_state = SS_DIRTY; } } return handle; } 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 */ } return handle; } static enum storage_err translate_errno(int error) { enum storage_err result; switch (error) { case 0: result = STORAGE_NO_ERROR; break; case EBADF: case EINVAL: case ENOTDIR: case EISDIR: case ENAMETOOLONG: result = STORAGE_ERR_NOT_VALID; break; case ENOENT: result = STORAGE_ERR_NOT_FOUND; break; case EEXIST: result = STORAGE_ERR_EXIST; break; case EPERM: case EACCES: result = STORAGE_ERR_ACCESS; break; default: result = STORAGE_ERR_GENERIC; break; } return result; } static ssize_t write_with_retry(int fd, const void *buf_, size_t size, off_t offset) { ssize_t rc; const uint8_t *buf = buf_; while (size > 0) { rc = TEMP_FAILURE_RETRY(pwrite(fd, buf, size, offset)); if (rc < 0) return rc; size -= rc; buf += rc; offset += rc; } return 0; } static ssize_t read_with_retry(int fd, void *buf_, size_t size, off_t offset) { ssize_t rc; size_t rcnt = 0; uint8_t *buf = buf_; while (size > 0) { rc = TEMP_FAILURE_RETRY(pread(fd, buf, size, offset)); if (rc < 0) return rc; if (rc == 0) break; size -= rc; buf += rc; offset += rc; rcnt += rc; } return rcnt; } int storage_file_delete(struct storage_msg* msg, const void* r, size_t req_len, struct watcher* watcher) { char *path = NULL; const struct storage_file_delete_req *req = r; if (req_len < sizeof(*req)) { ALOGE("%s: invalid request length (%zd < %zd)\n", __func__, req_len, sizeof(*req)); msg->result = STORAGE_ERR_NOT_VALID; goto err_response; } size_t fname_len = strlen(req->name); if (fname_len != req_len - sizeof(*req)) { ALOGE("%s: invalid filename length (%zd != %zd)\n", __func__, fname_len, req_len - sizeof(*req)); msg->result = STORAGE_ERR_NOT_VALID; goto err_response; } int rc = asprintf(&path, "%s/%s", ssdir_name, req->name); if (rc < 0) { ALOGE("%s: asprintf failed\n", __func__); msg->result = STORAGE_ERR_GENERIC; goto err_response; } watch_progress(watcher, "unlinking file"); rc = unlink(path); if (rc < 0) { rc = errno; if (errno == ENOENT) { ALOGV("%s: error (%d) unlinking file '%s'\n", __func__, rc, path); } else { ALOGE("%s: error (%d) unlinking file '%s'\n", __func__, rc, path); } msg->result = translate_errno(rc); goto err_response; } ALOGV("%s: \"%s\"\n", __func__, path); msg->result = STORAGE_NO_ERROR; err_response: if (path) free(path); return ipc_respond(msg, NULL, 0); } static void sync_parent(const char* path, struct watcher* watcher) { int parent_fd; watch_progress(watcher, "syncing parent"); char* parent_path = dirname(path); parent_fd = TEMP_FAILURE_RETRY(open(parent_path, O_RDONLY)); if (parent_fd >= 0) { fsync(parent_fd); close(parent_fd); } else { ALOGE("%s: failed to open parent directory \"%s\" for sync: %s\n", __func__, parent_path, strerror(errno)); } 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", __func__, req_len, sizeof(*req)); msg->result = STORAGE_ERR_NOT_VALID; goto err_response; } size_t fname_len = strlen(req->name); if (fname_len != req_len - sizeof(*req)) { ALOGE("%s: invalid filename length (%zd != %zd)\n", __func__, fname_len, req_len - sizeof(*req)); msg->result = STORAGE_ERR_NOT_VALID; goto err_response; } /* * TODO(b/210501710): Expose GSI image running state to vendor * storageproxyd. We want to control data file paths in vendor_init, but we * don't have access to the necessary property there yet. When we have * access to that property we can set the root data path read-only and only * allow creation of files in alternate/. Checking paths here temporarily * until that is fixed. * * We are just checking for "/" instead of "alternate/" because we still * want to still allow access to "persist/" in alternate mode (for now, this * may change in the future). */ if (alternate_mode && !strchr(req->name, '/')) { ALOGE("%s: Cannot open root data file \"%s\" in alternate mode\n", __func__, req->name); msg->result = STORAGE_ERR_ACCESS; goto err_response; } int rc = asprintf(&path, "%s/%s", ssdir_name, req->name); if (rc < 0) { ALOGE("%s: asprintf failed\n", __func__); msg->result = STORAGE_ERR_GENERIC; goto err_response; } int open_flags = O_RDWR; if (req->flags & STORAGE_FILE_OPEN_TRUNCATE) open_flags |= O_TRUNC; if (req->flags & STORAGE_FILE_OPEN_CREATE) { /* * Create the alternate parent dir if needed & allowed. * * TODO(b/210501710): Expose GSI image running state to vendor * storageproxyd. This directory should be created by vendor_init, once * it has access to the necessary bit of information. */ if (strstr(req->name, ALTERNATE_DATA_DIR) == req->name) { char* parent_path = dirname(path); rc = mkdir(parent_path, S_IRWXU); if (rc == 0) { sync_parent(parent_path, watcher); } else if (errno != EEXIST) { ALOGE("%s: Could not create parent directory \"%s\": %s\n", __func__, parent_path, strerror(errno)); } } /* open or create */ if (req->flags & STORAGE_FILE_OPEN_CREATE_EXCLUSIVE) { /* create exclusive */ open_flags |= O_CREAT | O_EXCL; /* 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; /* 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 { /* open an existing file */ rc = TEMP_FAILURE_RETRY(open(path, open_flags, S_IRUSR | S_IWUSR)); } if (rc < 0) { rc = errno; if (errno == EEXIST || errno == ENOENT) { ALOGV("%s: failed to open file \"%s\": %s\n", __func__, path, strerror(errno)); } else { ALOGE("%s: failed to open file \"%s\": %s\n", __func__, path, strerror(errno)); } msg->result = translate_errno(rc); goto err_response; } if (open_flags & O_CREAT) { sync_parent(path, watcher); } /* at this point rc contains storage file fd */ msg->result = STORAGE_NO_ERROR; resp.handle = insert_fd(open_flags, rc, mapping_entry); ALOGV("%s: \"%s\": fd = %u: handle = %d\n", __func__, path, rc, resp.handle); free(path); path = NULL; #ifdef VENDOR_FS_READY_PROPERTY /* a backing file has been opened, notify any waiting init steps */ if (!fs_ready_set || !fs_ready_rw_set) { bool is_checkpoint_active = false; rc = is_data_checkpoint_active(&is_checkpoint_active); if (rc != 0) { ALOGE("is_data_checkpoint_active() failed (%d)\n", rc); } else { if (!fs_ready_rw_set && !is_checkpoint_active) { fs_ready_rw_set = property_set_helper(FS_READY_RW_PROPERTY); } if (!fs_ready_set) { fs_ready_set = property_set_helper(FS_READY_PROPERTY); } } } #endif // #ifdef VENDOR_FS_READY_PROPERTY return ipc_respond(msg, &resp, sizeof(resp)); err_response: if (path) free(path); return ipc_respond(msg, NULL, 0); } int storage_file_close(struct storage_msg* msg, const void* r, size_t req_len, struct watcher* watcher) { const struct storage_file_close_req *req = r; if (req_len != sizeof(*req)) { ALOGE("%s: invalid request length (%zd != %zd)\n", __func__, req_len, sizeof(*req)); msg->result = STORAGE_ERR_NOT_VALID; goto err_response; } int fd = remove_fd(req->handle); ALOGV("%s: handle = %u: fd = %u\n", __func__, req->handle, fd); watch_progress(watcher, "fsyncing before file close"); int rc = fsync(fd); watch_progress(watcher, "done fsyncing before file close"); if (rc < 0) { rc = errno; ALOGE("%s: fsync failed for fd=%u: %s\n", __func__, fd, strerror(errno)); msg->result = translate_errno(rc); goto err_response; } rc = close(fd); if (rc < 0) { rc = errno; ALOGE("%s: close failed for fd=%u: %s\n", __func__, fd, strerror(errno)); msg->result = translate_errno(rc); goto err_response; } msg->result = STORAGE_NO_ERROR; err_response: return ipc_respond(msg, NULL, 0); } int storage_file_write(struct storage_msg* msg, const void* r, size_t req_len, struct watcher* watcher) { int rc; const struct storage_file_write_req *req = r; if (req_len < sizeof(*req)) { ALOGE("%s: invalid request length (%zd < %zd)\n", __func__, req_len, sizeof(*req)); msg->result = STORAGE_ERR_NOT_VALID; 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), req->offset) < 0) { watch_progress(watcher, "writing done w/ error"); rc = errno; ALOGW("%s: error writing file (fd=%d): %s\n", __func__, fd, strerror(errno)); msg->result = translate_errno(rc); goto err_response; } watch_progress(watcher, "writing done"); if (msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) { rc = storage_sync_checkpoint(watcher); if (rc < 0) { msg->result = STORAGE_ERR_SYNC_FAILURE; goto err_response; } } msg->result = STORAGE_NO_ERROR; err_response: return ipc_respond(msg, NULL, 0); } int storage_file_read(struct storage_msg* msg, const void* r, size_t req_len, struct watcher* watcher) { int rc; const struct storage_file_read_req *req = r; if (req_len != sizeof(*req)) { ALOGE("%s: invalid request length (%zd != %zd)\n", __func__, req_len, sizeof(*req)); msg->result = STORAGE_ERR_NOT_VALID; goto err_response; } if (req->size > MAX_READ_SIZE) { ALOGW("%s: request is too large (%u > %d) - refusing\n", __func__, req->size, MAX_READ_SIZE); msg->result = STORAGE_ERR_NOT_VALID; 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, (off_t)req->offset); watch_progress(watcher, "reading done"); if (read_res < 0) { rc = errno; ALOGW("%s: error reading file (fd=%d): %s\n", __func__, fd, strerror(errno)); msg->result = translate_errno(rc); goto err_response; } msg->result = STORAGE_NO_ERROR; return ipc_respond(msg, &read_rsp, read_res + sizeof(read_rsp.hdr)); err_response: return ipc_respond(msg, NULL, 0); } int storage_file_get_size(struct storage_msg* msg, const void* r, size_t req_len, struct watcher* watcher) { const struct storage_file_get_size_req *req = r; struct storage_file_get_size_resp resp = {0}; if (req_len != sizeof(*req)) { ALOGE("%s: invalid request length (%zd != %zd)\n", __func__, req_len, sizeof(*req)); msg->result = STORAGE_ERR_NOT_VALID; goto err_response; } struct stat stat; int fd = lookup_fd(req->handle, false); watch_progress(watcher, "fstat"); int rc = fstat(fd, &stat); watch_progress(watcher, "fstat done"); if (rc < 0) { rc = errno; ALOGE("%s: error stat'ing file (fd=%d): %s\n", __func__, fd, strerror(errno)); msg->result = translate_errno(rc); goto err_response; } resp.size = stat.st_size; msg->result = STORAGE_NO_ERROR; return ipc_respond(msg, &resp, sizeof(resp)); err_response: return ipc_respond(msg, NULL, 0); } int storage_file_set_size(struct storage_msg* msg, const void* r, size_t req_len, struct watcher* watcher) { const struct storage_file_set_size_req *req = r; if (req_len != sizeof(*req)) { ALOGE("%s: invalid request length (%zd != %zd)\n", __func__, req_len, sizeof(*req)); msg->result = STORAGE_ERR_NOT_VALID; goto err_response; } int fd = lookup_fd(req->handle, true); watch_progress(watcher, "ftruncate"); int rc = TEMP_FAILURE_RETRY(ftruncate(fd, req->size)); watch_progress(watcher, "ftruncate done"); if (rc < 0) { rc = errno; ALOGE("%s: error truncating file (fd=%d): %s\n", __func__, fd, strerror(errno)); msg->result = translate_errno(rc); goto err_response; } msg->result = STORAGE_NO_ERROR; err_response: return ipc_respond(msg, NULL, 0); } int storage_file_get_max_size(struct storage_msg* msg, const void* r, size_t req_len, struct watcher* watcher) { const struct storage_file_get_max_size_req* req = r; struct storage_file_get_max_size_resp resp = {0}; uint64_t max_size = 0; if (req_len != sizeof(*req)) { ALOGE("%s: invalid request length (%zd != %zd)\n", __func__, req_len, sizeof(*req)); msg->result = STORAGE_ERR_NOT_VALID; goto err_response; } struct stat stat; int fd = lookup_fd(req->handle, false); watch_progress(watcher, "fstat to get max size"); int rc = fstat(fd, &stat); watch_progress(watcher, "fstat to get max size done"); if (rc < 0) { ALOGE("%s: error stat'ing file (fd=%d): %s\n", __func__, fd, strerror(errno)); goto err_response; } if ((stat.st_mode & S_IFMT) == S_IFBLK) { rc = ioctl(fd, BLKGETSIZE64, &max_size); if (rc < 0) { rc = errno; ALOGE("%s: error calling ioctl on file (fd=%d): %s\n", __func__, fd, strerror(errno)); msg->result = translate_errno(rc); goto err_response; } } else { max_size = max_file_size; } resp.max_size = max_size; msg->result = STORAGE_NO_ERROR; return ipc_respond(msg, &resp, sizeof(resp)); err_response: return ipc_respond(msg, NULL, 0); } 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%" PRIx64 "\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%" PRIx64 " 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 */ } 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; } int storage_sync_checkpoint(struct watcher* watcher) { int rc; 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 */ } } /* check if we need to sync all filesystems */ if (fs_state == SS_DIRTY) { /* * We sync all filesystems here because we don't know what filesystem * needs syncing if there happen to be other filesystems symlinked under * the root data directory. This should not happen in the normal case * because our fd table is large enough to handle the few open files we * use. */ watch_progress(watcher, "all fs sync"); sync(); fs_state = SS_CLEAN; } watch_progress(watcher, "done syncing"); return 0; }