/* Copyright 2013-2014 IBM Corp. * * 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. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libflash.h" #include "blocklevel.h" struct file_data { int fd; char *name; struct blocklevel_device bl; }; static int file_read(struct blocklevel_device *bl, uint32_t pos, void *buf, uint32_t len) { struct file_data *file_data = container_of(bl, struct file_data, bl); int count = 0; int rc; rc = lseek(file_data->fd, pos, SEEK_SET); /* errno should remain set */ if (rc != pos) return FLASH_ERR_PARM_ERROR; while (count < len) { rc = read(file_data->fd, buf, len); /* errno should remain set */ if (rc == -1) return FLASH_ERR_BAD_READ; count += rc; } return 0; } static int file_write(struct blocklevel_device *bl, uint32_t dst, const void *src, uint32_t len) { struct file_data *file_data = container_of(bl, struct file_data, bl); int count = 0; int rc; rc = lseek(file_data->fd, dst, SEEK_SET); /* errno should remain set */ if (rc == -1) return FLASH_ERR_PARM_ERROR; while (count < len) { rc = write(file_data->fd, src, len); /* errno should remain set */ if (rc == -1) return FLASH_ERR_VERIFY_FAILURE; count += rc; } return 0; } /* * Due to to the fact these interfaces are ultimately supposed to deal with * flash, an erase function must be implemented even when the flash images * are backed by regular files. * Also, erasing flash leaves all the bits set to 1. This may be expected * by higher level functions so this function should also emulate that */ static int file_erase(struct blocklevel_device *bl, uint32_t dst, uint32_t len) { unsigned long long int d = ULLONG_MAX; int i = 0; int rc = 0; while (len - i > 0) { rc = file_write(bl, dst + i, &d, len - i > sizeof(d) ? sizeof(d) : len - i); if (rc) return rc; i += sizeof(d); } return 0; } static int mtd_erase(struct blocklevel_device *bl, uint32_t dst, uint32_t len) { struct file_data *file_data = container_of(bl, struct file_data, bl); struct erase_info_user erase_info = { .start = dst, .length = len }; return ioctl(file_data->fd, MEMERASE, &erase_info) == -1 ? -1 : 0; } static int get_info_name(struct file_data *file_data, char **name) { char *path, *lpath; int len; struct stat st; if (asprintf(&path, "/proc/self/fd/%d", file_data->fd) == -1) return FLASH_ERR_MALLOC_FAILED; if (lstat(path, &st)) { free(path); return FLASH_ERR_PARM_ERROR; } lpath = malloc(st.st_size + 1); if (!lpath) { free(path); return FLASH_ERR_MALLOC_FAILED; } len = readlink(path, lpath, st.st_size +1); if (len == -1) { free(path); free(lpath); return FLASH_ERR_PARM_ERROR; } lpath[len] = '\0'; *name = lpath; free(path); return 0; } static int mtd_get_info(struct blocklevel_device *bl, const char **name, uint32_t *total_size, uint32_t *erase_granule) { struct file_data *file_data = container_of(bl, struct file_data, bl); struct mtd_info_user mtd_info; int rc; if (ioctl(file_data->fd, MEMGETINFO, &mtd_info) == -1) return FLASH_ERR_BAD_READ; if (total_size) *total_size = mtd_info.size; if (erase_granule) *erase_granule = mtd_info.erasesize; if (name) { rc = get_info_name(file_data, &(file_data->name)); if (rc) return rc; *name = file_data->name; } return 0; } static int file_get_info(struct blocklevel_device *bl, const char **name, uint32_t *total_size, uint32_t *erase_granule) { struct file_data *file_data = container_of(bl, struct file_data, bl); struct stat st; int rc; if (fstat(file_data->fd, &st)) return FLASH_ERR_PARM_ERROR; if (total_size) *total_size = st.st_size; if (erase_granule) *erase_granule = 1; if (name) { rc = get_info_name(file_data, &(file_data->name)); if (rc) return rc; *name = file_data->name; } return 0; } int file_init(int fd, struct blocklevel_device **bl) { struct file_data *file_data; struct stat sbuf; if (!bl) return FLASH_ERR_PARM_ERROR; *bl = NULL; file_data = malloc(sizeof(struct file_data)); if (!file_data) return FLASH_ERR_MALLOC_FAILED; memset(file_data, 0, sizeof(*file_data)); file_data->fd = fd; file_data->bl.read = &file_read; file_data->bl.write = &file_write; file_data->bl.erase = &file_erase; file_data->bl.get_info = &file_get_info; file_data->bl.erase_mask = 0; /* * Unfortunately not all file descriptors are created equal... * Here we check to see if the file descriptor is to an MTD device, in * which case we have to erase and get the size of it differently. */ if (fstat(file_data->fd, &sbuf) == -1) goto out; /* Won't be able to handle other than MTD devices for now */ if (S_ISCHR(sbuf.st_mode)) { file_data->bl.erase = &mtd_erase; file_data->bl.get_info = &mtd_get_info; file_data->bl.flags = WRITE_NEED_ERASE; mtd_get_info(&file_data->bl, NULL, NULL, &(file_data->bl.erase_mask)); file_data->bl.erase_mask--; } else if (!S_ISREG(sbuf.st_mode)) { /* If not a char device or a regular file something went wrong */ goto out; } *bl = &(file_data->bl); return 0; out: free(file_data); return FLASH_ERR_PARM_ERROR; } int file_init_path(const char *path, int *r_fd, struct blocklevel_device **bl) { int fd, rc; if (!path || !bl) return FLASH_ERR_PARM_ERROR; fd = open(path, O_RDWR); if (fd == -1) return FLASH_ERR_PARM_ERROR; rc = file_init(fd, bl); if (rc) close(fd); if (r_fd) *r_fd = fd; return rc; } void file_exit(struct blocklevel_device *bl) { struct file_data *file_data; if (bl) { file_data = container_of(bl, struct file_data, bl); free(file_data->name); free(file_data); } } void file_exit_close(struct blocklevel_device *bl) { struct file_data *file_data; if (bl) { file_data = container_of(bl, struct file_data, bl); close(file_data->fd); file_exit(bl); } }