| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Permission is hereby granted, free of charge, to any person |
| * obtaining a copy of this software and associated documentation |
| * files (the "Software"), to deal in the Software without |
| * restriction, including without limitation the rights to use, copy, |
| * modify, merge, publish, distribute, sublicense, and/or sell copies |
| * of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <linux/fs.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <cutils/properties.h> |
| #include <fs_mgr.h> |
| |
| #include "avb_ops_device.h" |
| |
| /* Open the appropriate fstab file and fallback to /fstab.device if |
| * that's what's being used. |
| */ |
| static struct fstab* open_fstab(void) { |
| char propbuf[PROPERTY_VALUE_MAX]; |
| char fstab_name[PROPERTY_VALUE_MAX + 32]; |
| struct fstab* fstab; |
| |
| property_get("ro.hardware", propbuf, ""); |
| snprintf(fstab_name, sizeof(fstab_name), "/fstab.%s", propbuf); |
| fstab = fs_mgr_read_fstab(fstab_name); |
| if (fstab != NULL) { |
| return fstab; |
| } |
| |
| fstab = fs_mgr_read_fstab("/fstab.device"); |
| return fstab; |
| } |
| |
| static int open_partition(const char* name, int flags) { |
| char* path; |
| int fd; |
| struct fstab* fstab; |
| struct fstab_rec* record; |
| |
| /* We can't use fs_mgr to look up |name| because fstab doesn't list |
| * every slot partition (it uses the slotselect option to mask the |
| * suffix) and |slot| is expected to be of that form, e.g. boot_a. |
| * |
| * We can however assume that there's an entry for the /misc mount |
| * point and use that to get the device file for the misc |
| * partition. From there we'll assume that a by-name scheme is used |
| * so we can just replace the trailing "misc" by the given |name|, |
| * e.g. |
| * |
| * /dev/block/platform/soc.0/7824900.sdhci/by-name/misc -> |
| * /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a |
| * |
| * If needed, it's possible to relax this assumption in the future |
| * by trawling /sys/block looking for the appropriate sibling of |
| * misc and then finding an entry in /dev matching the sysfs entry. |
| */ |
| |
| fstab = open_fstab(); |
| if (fstab == NULL) { |
| return -1; |
| } |
| record = fs_mgr_get_entry_for_mount_point(fstab, "/misc"); |
| if (record == NULL) { |
| fs_mgr_free_fstab(fstab); |
| return -1; |
| } |
| if (strcmp(name, "misc") == 0) { |
| path = strdup(record->blk_device); |
| } else { |
| size_t trimmed_len, name_len; |
| const char* end_slash = strrchr(record->blk_device, '/'); |
| if (end_slash == NULL) { |
| fs_mgr_free_fstab(fstab); |
| return -1; |
| } |
| trimmed_len = end_slash - record->blk_device + 1; |
| name_len = strlen(name); |
| path = calloc(trimmed_len + name_len + 1, 1); |
| strncpy(path, record->blk_device, trimmed_len); |
| strncpy(path + trimmed_len, name, name_len); |
| } |
| fs_mgr_free_fstab(fstab); |
| |
| fd = open(path, flags); |
| free(path); |
| |
| return fd; |
| } |
| |
| static AvbIOResult read_from_partition(AvbOps* ops, |
| const char* partition, |
| int64_t offset, |
| size_t num_bytes, |
| void* buffer, |
| size_t* out_num_read) { |
| int fd; |
| off_t where; |
| ssize_t num_read; |
| AvbIOResult ret; |
| |
| fd = open_partition(partition, O_RDONLY); |
| if (fd == -1) { |
| avb_errorv("Error opening \"", partition, "\" partition.\n", NULL); |
| ret = AVB_IO_RESULT_ERROR_IO; |
| goto out; |
| } |
| |
| where = lseek(fd, offset, SEEK_SET); |
| if (where == -1) { |
| avb_error("Error seeking to offset.\n"); |
| ret = AVB_IO_RESULT_ERROR_IO; |
| goto out; |
| } |
| if (where != offset) { |
| avb_error("Error seeking to offset.\n"); |
| ret = AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION; |
| goto out; |
| } |
| |
| /* On Linux, we never get partial reads from block devices (except |
| * for EOF). |
| */ |
| num_read = read(fd, buffer, num_bytes); |
| if (num_read == -1) { |
| avb_error("Error reading data.\n"); |
| ret = AVB_IO_RESULT_ERROR_IO; |
| goto out; |
| } |
| if (out_num_read != NULL) { |
| *out_num_read = num_read; |
| } |
| |
| ret = AVB_IO_RESULT_OK; |
| |
| out: |
| if (fd != -1) { |
| if (close(fd) != 0) { |
| avb_error("Error closing file descriptor.\n"); |
| } |
| } |
| return ret; |
| } |
| |
| static AvbIOResult write_to_partition(AvbOps* ops, |
| const char* partition, |
| int64_t offset, |
| size_t num_bytes, |
| const void* buffer) { |
| int fd; |
| off_t where; |
| ssize_t num_written; |
| AvbIOResult ret; |
| |
| fd = open_partition(partition, O_WRONLY); |
| if (fd == -1) { |
| avb_errorv("Error opening \"", partition, "\" partition.\n", NULL); |
| ret = AVB_IO_RESULT_ERROR_IO; |
| goto out; |
| } |
| |
| where = lseek(fd, offset, SEEK_SET); |
| if (where == -1) { |
| avb_error("Error seeking to offset.\n"); |
| ret = AVB_IO_RESULT_ERROR_IO; |
| goto out; |
| } |
| if (where != offset) { |
| avb_error("Error seeking to offset.\n"); |
| ret = AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION; |
| goto out; |
| } |
| |
| /* On Linux, we never get partial writes on block devices. */ |
| num_written = write(fd, buffer, num_bytes); |
| if (num_written == -1) { |
| avb_error("Error writing data.\n"); |
| ret = AVB_IO_RESULT_ERROR_IO; |
| goto out; |
| } |
| |
| ret = AVB_IO_RESULT_OK; |
| |
| out: |
| if (fd != -1) { |
| if (close(fd) != 0) { |
| avb_error("Error closing file descriptor.\n"); |
| } |
| } |
| return ret; |
| } |
| |
| AvbABOps* avb_ops_device_new(void) { |
| AvbABOps* ab_ops; |
| |
| ab_ops = calloc(1, sizeof(AvbABOps)); |
| if (ab_ops == NULL) { |
| avb_error("Error allocating memory for AvbABOps.\n"); |
| goto out; |
| } |
| |
| ab_ops->ops = calloc(1, sizeof(AvbOps)); |
| if (ab_ops->ops == NULL) { |
| avb_error("Error allocating memory for AvbOps.\n"); |
| free(ab_ops); |
| goto out; |
| } |
| |
| /* We only need these operations since that's all what is being used |
| * by the A/B routines. |
| */ |
| ab_ops->ops->read_from_partition = read_from_partition; |
| ab_ops->ops->write_to_partition = write_to_partition; |
| ab_ops->read_ab_metadata = avb_ab_data_read; |
| ab_ops->write_ab_metadata = avb_ab_data_write; |
| |
| out: |
| return ab_ops; |
| } |
| |
| void avb_ops_device_free(AvbABOps* ab_ops) { |
| free(ab_ops->ops); |
| free(ab_ops); |
| } |