From: Jeff Mahoney Subject: initramfs: add initramfs_{read,write} References: bnc#568120 Patch-mainline: Probably never This patch adds initramfs_read and initramfs_write, which will read and write to the initramfs without traversing huge chunks of the VFS code. A previous incarnation of the ACPI dynamic table patches ended up causing "scheduling while atomic" warnings during boot, resulting in a whole lot of bug reports. Signed-off-by: Jeff Mahoney --- init/initramfs.c | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 147 insertions(+), 3 deletions(-) --- a/init/initramfs.c +++ b/init/initramfs.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -8,6 +9,8 @@ #include #include #include +#include +#include static __initdata char *message; static void __init error(char *x) @@ -333,10 +335,152 @@ static int __init do_name(void) return 0; } +ssize_t initramfs_file_read(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct address_space *mapping = file->f_mapping; + struct iovec iov = { .iov_base = (void __user *) buf, + .iov_len = count }; + struct iov_iter i; + long status = 0; + loff_t pos = *ppos; + ssize_t read = 0; + + iov_iter_init(&i, &iov, 1, count, 0); + + do { + struct page *page; + pgoff_t index; + unsigned long offset; + unsigned long bytes; + char *data; + + offset = (pos & (PAGE_CACHE_SIZE - 1)); + index = pos >> PAGE_CACHE_SHIFT; + bytes = min_t(unsigned long, PAGE_CACHE_SIZE - offset, + iov_iter_count(&i)); + + page = read_mapping_page(mapping, index, NULL); + if (IS_ERR(page)) { + status = PTR_ERR(page); + break; + } + + data = page_address(page); + memcpy(i.iov->iov_base + i.iov_offset, data + offset, bytes); + + iov_iter_advance(&i, bytes); + pos += bytes; + read += bytes; + } while (iov_iter_count(&i)); + + *ppos = pos; + + return read ? read : status; +} + +ssize_t initramfs_file_write(struct file *file, const char * __user buf, + size_t count, loff_t *ppos) +{ + struct address_space *mapping = file->f_mapping; + struct iovec iov = { .iov_base = (void __user *) buf, + .iov_len = count }; + long status = 0; + ssize_t written = 0; + unsigned int flags = 0; + loff_t pos = *ppos; + struct iov_iter i; + + iov_iter_init(&i, &iov, 1, count, 0); + + /* + * Copies from kernel address space cannot fail (NFSD is a big user). + */ + if (segment_eq(get_fs(), KERNEL_DS)) + flags |= AOP_FLAG_UNINTERRUPTIBLE; + + mutex_lock(&mapping->host->i_mutex); + + do { + struct page *page; + pgoff_t index; /* Pagecache index for current page */ + unsigned long offset; /* Offset into pagecache page */ + unsigned long bytes; /* Bytes to write to page */ + size_t copied; /* Bytes copied from user */ + void *fsdata; + char *data; + + offset = (pos & (PAGE_CACHE_SIZE - 1)); + index = pos >> PAGE_CACHE_SHIFT; + bytes = min_t(unsigned long, PAGE_CACHE_SIZE - offset, + iov_iter_count(&i)); + + status = simple_write_begin(file, mapping, pos, bytes, flags, + &page, &fsdata); + if (unlikely(status)) + break; + data = page_address(page); + + memcpy(data + offset, i.iov->iov_base + i.iov_offset, bytes); + copied = bytes; + + status = simple_write_end(file, mapping, pos, bytes, copied, + page, fsdata); + if (unlikely(status < 0)) + break; + copied = status; + + iov_iter_advance(&i, copied); + pos += copied; + written += copied; + + } while (iov_iter_count(&i)); + + mutex_unlock(&mapping->host->i_mutex); + + *ppos = pos; + + return written ? written : status; +} + +ssize_t +initramfs_read(unsigned int fd, const char * buf, size_t count) +{ + struct file *file; + ssize_t ret = 0; + + file = fget(fd); + if (file) { + loff_t pos = file->f_pos; + ret = initramfs_file_read(file, buf, count, &pos); + file->f_pos = pos; + fput(file); + } + + return ret; +} + +ssize_t +initramfs_write(unsigned int fd, const char * buf, size_t count) +{ + struct file *file; + ssize_t ret = 0; + + file = fget(fd); + if (file) { + loff_t pos = file->f_pos; + ret = initramfs_file_write(file, buf, count, &pos); + file->f_pos = pos; + fput(file); + } + + return ret; +} + static int __init do_copy(void) { if (count >= body_len) { - sys_write(wfd, victim, body_len); + initramfs_write(wfd, victim, body_len); sys_close(wfd); do_utime(vcollected, mtime); kfree(vcollected); @@ -344,7 +488,7 @@ static int __init do_copy(void) state = SkipIt; return 0; } else { - sys_write(wfd, victim, count); + initramfs_write(wfd, victim, count); body_len -= count; eat(count); return 1; @@ -589,7 +733,7 @@ static int __init populate_rootfs(void) "; looks like an initrd\n", err); fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700); if (fd >= 0) { - sys_write(fd, (char *)initrd_start, + initramfs_write(fd, (char *)initrd_start, initrd_end - initrd_start); sys_close(fd); free_initrd();