/* 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 "libflash/file.h" #include "blocklevel.h" struct file_data { int fd; char *name; char *path; struct blocklevel_device bl; }; static int file_release(struct blocklevel_device *bl) { struct file_data *file_data = container_of(bl, struct file_data, bl); close(file_data->fd); file_data->fd = -1; return 0; } static int file_reacquire(struct blocklevel_device *bl) { struct file_data *file_data = container_of(bl, struct file_data, bl); int fd; fd = open(file_data->path, O_RDWR); if (fd == -1) return FLASH_ERR_PARM_ERROR; file_data->fd = fd; return 0; } static int file_read(struct blocklevel_device *bl, uint64_t pos, void *buf, uint64_t len) { struct file_data *file_data = container_of(bl, struct file_data, bl); int rc, count = 0; 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 - count); /* errno should remain set */ if (rc == -1 || rc == 0) return FLASH_ERR_BAD_READ; buf += rc; count += rc; } return 0; } static int file_write(struct blocklevel_device *bl, uint64_t dst, const void *src, uint64_t len) { struct file_data *file_data = container_of(bl, struct file_data, bl); int rc, count = 0; rc = lseek(file_data->fd, dst, SEEK_SET); /* errno should remain set */ if (rc != dst) return FLASH_ERR_PARM_ERROR; while (count < len) { rc = write(file_data->fd, src, len - count); /* errno should remain set */ if (rc == -1) return FLASH_ERR_VERIFY_FAILURE; src += rc; 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, uint64_t dst, uint64_t len) { static char buf[4096]; int i = 0; int rc; memset(buf, ~0, sizeof(buf)); while (len - i > 0) { rc = file_write(bl, dst + i, buf, len - i > sizeof(buf) ? sizeof(buf) : len - i); if (rc) return rc; i += (len - i > sizeof(buf)) ? sizeof(buf) : len - i; } return 0; } static int mtd_erase(struct blocklevel_device *bl, uint64_t dst, uint64_t len) { struct file_data *file_data = container_of(bl, struct file_data, bl); int err; FL_DBG("%s: dst: 0x%" PRIx64 ", len: 0x%" PRIx64 "\n", __func__, dst, len); /* * Some kernels that pflash supports do not know about the 64bit * version of the ioctl() therefore we'll just use the 32bit (which * should always be supported...) unless we MUST use the 64bit and * then lets just hope the kernel knows how to deal with it. If it * is unsupported the ioctl() will fail and we'll report that - * there is no other option. * * Furthermore, even very recent MTD layers and drivers aren't * particularly good at not blocking in the kernel. This creates * unexpected behaviour in userspace tools using these functions. * In the absence of significant work inside the kernel, we'll just * split stuff up here for convenience. * We can assume everything is aligned here. */ while (len) { if (dst > UINT_MAX || len > UINT_MAX) { struct erase_info_user64 erase_info = { .start = dst, .length = file_data->bl.erase_mask + 1 }; if (ioctl(file_data->fd, MEMERASE64, &erase_info) == -1) { err = errno; if (err == 25) /* Kernel doesn't do 64bit MTD erase ioctl() */ FL_DBG("Attempted a 64bit erase on a kernel which doesn't support it\n"); FL_ERR("%s: IOCTL to kernel failed! %s\n", __func__, strerror(err)); errno = err; return FLASH_ERR_PARM_ERROR; } } else { struct erase_info_user erase_info = { .start = dst, .length = file_data->bl.erase_mask + 1 }; if (ioctl(file_data->fd, MEMERASE, &erase_info) == -1) { err = errno; FL_ERR("%s: IOCTL to kernel failed! %s\n", __func__, strerror(err)); errno = err; return FLASH_ERR_PARM_ERROR; } } dst += file_data->bl.erase_mask + 1; len -= file_data->bl.erase_mask + 1; } return 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, uint64_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; rc = ioctl(file_data->fd, MEMGETINFO, &mtd_info); if (rc == -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, uint64_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 = calloc(1, sizeof(struct file_data)); if (!file_data) return FLASH_ERR_MALLOC_FAILED; file_data->fd = fd; file_data->bl.reacquire = &file_reacquire; file_data->bl.release = &file_release; 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; /* * If the blocklevel_device is only inited with file_init() then keep * alive is assumed, as fd will change otherwise and this may break * callers assumptions. */ file_data->bl.keep_alive = 1; /* * 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, bool keep_alive, struct blocklevel_device **bl) { int fd, rc; char *path_ptr = NULL; struct file_data *file_data; if (!path || !bl) return FLASH_ERR_PARM_ERROR; fd = open(path, O_RDWR); if (fd == -1) return FLASH_ERR_PARM_ERROR; /* * strdup() first so don't have to deal with malloc failure after * file_init() */ path_ptr = strdup(path); if (!path_ptr) { rc = FLASH_ERR_MALLOC_FAILED; goto out; } rc = file_init(fd, bl); if (rc) goto out; file_data = container_of(*bl, struct file_data, bl); file_data->bl.keep_alive = keep_alive; file_data->path = path_ptr; if (r_fd) *r_fd = fd; return rc; out: free(path_ptr); close(fd); return rc; } void file_exit(struct blocklevel_device *bl) { struct file_data *file_data; if (bl) { free(bl->ecc_prot.prot); file_data = container_of(bl, struct file_data, bl); free(file_data->name); free(file_data->path); 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); } }