diff options
-rw-r--r-- | libflash/file.c | 294 | ||||
-rw-r--r-- | libflash/file.h | 45 |
2 files changed, 339 insertions, 0 deletions
diff --git a/libflash/file.c b/libflash/file.c new file mode 100644 index 0000000..df11271 --- /dev/null +++ b/libflash/file.c @@ -0,0 +1,294 @@ +/* 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> + +#include <ccan/container_of/container_of.h> + +#include <mtd/mtd-abi.h> + +#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); + } +} diff --git a/libflash/file.h b/libflash/file.h new file mode 100644 index 0000000..a9a89fe --- /dev/null +++ b/libflash/file.h @@ -0,0 +1,45 @@ +/* Copyright 2013-2015 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. + */ + +#ifndef __LIBFLASH_FILE_H +#define __LIBFLASH_FILE_H + +#include "blocklevel.h" + +/* + * Blockevel functions created leave errno set on errors, as these calls + * often boil down to standard read() and write() calls, inspecting errno + * may prove useful + */ + +int file_init(int fd, struct blocklevel_device **bl); +void file_exit(struct blocklevel_device *bl); + +/* + * file_init_path() is a convenience wrapper which will open the path and call + * file_init(). The call to open happens with O_RDWR and no additional flags + * Because file_exit() doesn't close the file descriptor, file_init_path() + * makes it available. + */ +int file_init_path(const char *path, int *fd, struct blocklevel_device **bl); + +/* + * file_exit_close is a convenience wrapper which will close the open + * file descriptor and call file_exit(). + */ +void file_exit_close(struct blocklevel_device *bl); + +#endif /* __LIBFLASH_FILE_H */ |