diff mbox series

[RFC,v2,1/7] initramfs: Move cpio handling routines into lib/

Message ID bbf0513a72c6f7e06dea38b04a606a3e739ce82d.1659003817.git.noodles@fb.com (mailing list archive)
State New, archived
Headers show
Series [RFC,v2,1/7] initramfs: Move cpio handling routines into lib/ | expand

Commit Message

Jonathan McDowell July 28, 2022, 2:09 p.m. UTC
The cpio handling routines can be useful outside of just initialising
the initramfs. Pull the functions into lib/ and all of the static state
into a context structure in preparation for enabling the use of this
functionality outside of __init code.

Signed-off-by: Jonathan McDowell <noodles@fb.com>
---
v2:
- Fix printf format string in populate_initrd_image (kernel test robot, i386 build)
- Include <linux/limits.h> in cpio.h (kernel test robot, uml build)
---
 include/linux/cpio.h |  79 +++++++
 init/initramfs.c     | 518 ++++---------------------------------------
 lib/Makefile         |   2 +-
 lib/cpio.c           | 454 +++++++++++++++++++++++++++++++++++++
 4 files changed, 579 insertions(+), 474 deletions(-)
 create mode 100644 include/linux/cpio.h
 create mode 100644 lib/cpio.c
diff mbox series

Patch

diff --git a/include/linux/cpio.h b/include/linux/cpio.h
new file mode 100644
index 000000000000..2f9fd735331e
--- /dev/null
+++ b/include/linux/cpio.h
@@ -0,0 +1,79 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_CPIO_H
+#define _LINUX_CPIO_H
+
+#include <linux/init.h>
+#include <linux/limits.h>
+#include <linux/time.h>
+#include <linux/types.h>
+
+/* Must be a power of 2 */
+#define CPIO_LINK_HASH_SIZE	32
+
+#define N_ALIGN(len) ((((len) + 1) & ~3) + 2)
+
+enum cpio_state {
+	CPIO_START,
+	CPIO_COLLECT,
+	CPIO_GOTHEADER,
+	CPIO_SKIPIT,
+	CPIO_GOTNAME,
+	CPIO_COPYFILE,
+	CPIO_GOTSYMLINK,
+	CPIO_RESET
+};
+
+struct cpio_dir_entry {
+	struct list_head list;
+	time64_t mtime;
+	char name[];
+};
+
+struct cpio_link_hash {
+	int ino, minor, major;
+	umode_t mode;
+	struct cpio_link_hash *next;
+	char name[N_ALIGN(PATH_MAX)];
+};
+
+struct cpio_context {
+	enum cpio_state state, next_state;
+	char *header_buf, *symlink_buf, *name_buf;
+	loff_t this_header, next_header;
+	char *victim;
+	unsigned long byte_count;
+	bool csum_present;
+	u32 io_csum;
+	char *errmsg;
+
+	char *collected;
+	long remains;
+	char *collect;
+
+	struct file *wfile;
+	loff_t wfile_pos;
+
+	/* Header fields */
+	unsigned long ino, major, minor, nlink;
+	umode_t mode;
+	unsigned long body_len, name_len;
+	uid_t uid;
+	gid_t gid;
+	unsigned int rdev;
+	u32 hdr_csum;
+	time64_t mtime;
+
+	/* Link hash */
+	struct cpio_link_hash *link_hash[CPIO_LINK_HASH_SIZE];
+
+	struct list_head dir_list;
+};
+
+int __init cpio_start(struct cpio_context *ctx);
+void __init cpio_finish(struct cpio_context *ctx);
+long __init cpio_write_buffer(struct cpio_context *ctx, char *buf,
+			      unsigned long len);
+long __init cpio_process_buffer(struct cpio_context *ctx, void *bufv,
+				unsigned long len);
+
+#endif /* _LINUX_CPIO_H */
diff --git a/init/initramfs.c b/init/initramfs.c
index 18229cfe8906..00c101d04f4b 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -1,7 +1,6 @@ 
 // SPDX-License-Identifier: GPL-2.0
 #include <linux/init.h>
 #include <linux/async.h>
-#include <linux/fs.h>
 #include <linux/slab.h>
 #include <linux/types.h>
 #include <linux/fcntl.h>
@@ -14,43 +13,9 @@ 
 #include <linux/memblock.h>
 #include <linux/mm.h>
 #include <linux/namei.h>
-#include <linux/init_syscalls.h>
 #include <linux/task_work.h>
 #include <linux/umh.h>
-
-static __initdata bool csum_present;
-static __initdata u32 io_csum;
-
-static ssize_t __init xwrite(struct file *file, const unsigned char *p,
-		size_t count, loff_t *pos)
-{
-	ssize_t out = 0;
-
-	/* sys_write only can write MAX_RW_COUNT aka 2G-4K bytes at most */
-	while (count) {
-		ssize_t rv = kernel_write(file, p, count, pos);
-
-		if (rv < 0) {
-			if (rv == -EINTR || rv == -EAGAIN)
-				continue;
-			return out ? out : rv;
-		} else if (rv == 0)
-			break;
-
-		if (csum_present) {
-			ssize_t i;
-
-			for (i = 0; i < rv; i++)
-				io_csum += p[i];
-		}
-
-		p += rv;
-		out += rv;
-		count -= rv;
-	}
-
-	return out;
-}
+#include <linux/cpio.h>
 
 static __initdata char *message;
 static void __init error(char *x)
@@ -69,423 +34,17 @@  static void panic_show_mem(const char *fmt, ...)
 	va_end(args);
 }
 
-/* link hash */
-
-#define N_ALIGN(len) ((((len) + 1) & ~3) + 2)
-
-static __initdata struct hash {
-	int ino, minor, major;
-	umode_t mode;
-	struct hash *next;
-	char name[N_ALIGN(PATH_MAX)];
-} *head[32];
-
-static inline int hash(int major, int minor, int ino)
-{
-	unsigned long tmp = ino + minor + (major << 3);
-	tmp += tmp >> 5;
-	return tmp & 31;
-}
-
-static char __init *find_link(int major, int minor, int ino,
-			      umode_t mode, char *name)
-{
-	struct hash **p, *q;
-	for (p = head + hash(major, minor, ino); *p; p = &(*p)->next) {
-		if ((*p)->ino != ino)
-			continue;
-		if ((*p)->minor != minor)
-			continue;
-		if ((*p)->major != major)
-			continue;
-		if (((*p)->mode ^ mode) & S_IFMT)
-			continue;
-		return (*p)->name;
-	}
-	q = kmalloc(sizeof(struct hash), GFP_KERNEL);
-	if (!q)
-		panic_show_mem("can't allocate link hash entry");
-	q->major = major;
-	q->minor = minor;
-	q->ino = ino;
-	q->mode = mode;
-	strcpy(q->name, name);
-	q->next = NULL;
-	*p = q;
-	return NULL;
-}
-
-static void __init free_hash(void)
-{
-	struct hash **p, *q;
-	for (p = head; p < head + 32; p++) {
-		while (*p) {
-			q = *p;
-			*p = q->next;
-			kfree(q);
-		}
-	}
-}
-
-#ifdef CONFIG_INITRAMFS_PRESERVE_MTIME
-static void __init do_utime(char *filename, time64_t mtime)
-{
-	struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
-	init_utimes(filename, t);
-}
-
-static void __init do_utime_path(const struct path *path, time64_t mtime)
-{
-	struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
-	vfs_utimes(path, t);
-}
-
-static __initdata LIST_HEAD(dir_list);
-struct dir_entry {
-	struct list_head list;
-	time64_t mtime;
-	char name[];
-};
-
-static void __init dir_add(const char *name, time64_t mtime)
-{
-	size_t nlen = strlen(name) + 1;
-	struct dir_entry *de;
-
-	de = kmalloc(sizeof(struct dir_entry) + nlen, GFP_KERNEL);
-	if (!de)
-		panic_show_mem("can't allocate dir_entry buffer");
-	INIT_LIST_HEAD(&de->list);
-	strscpy(de->name, name, nlen);
-	de->mtime = mtime;
-	list_add(&de->list, &dir_list);
-}
-
-static void __init dir_utime(void)
-{
-	struct dir_entry *de, *tmp;
-	list_for_each_entry_safe(de, tmp, &dir_list, list) {
-		list_del(&de->list);
-		do_utime(de->name, de->mtime);
-		kfree(de);
-	}
-}
-#else
-static void __init do_utime(char *filename, time64_t mtime) {}
-static void __init do_utime_path(const struct path *path, time64_t mtime) {}
-static void __init dir_add(const char *name, time64_t mtime) {}
-static void __init dir_utime(void) {}
-#endif
-
-static __initdata time64_t mtime;
-
-/* cpio header parsing */
-
-static __initdata unsigned long ino, major, minor, nlink;
-static __initdata umode_t mode;
-static __initdata unsigned long body_len, name_len;
-static __initdata uid_t uid;
-static __initdata gid_t gid;
-static __initdata unsigned rdev;
-static __initdata u32 hdr_csum;
-
-static void __init parse_header(char *s)
-{
-	unsigned long parsed[13];
-	char buf[9];
-	int i;
-
-	buf[8] = '\0';
-	for (i = 0, s += 6; i < 13; i++, s += 8) {
-		memcpy(buf, s, 8);
-		parsed[i] = simple_strtoul(buf, NULL, 16);
-	}
-	ino = parsed[0];
-	mode = parsed[1];
-	uid = parsed[2];
-	gid = parsed[3];
-	nlink = parsed[4];
-	mtime = parsed[5]; /* breaks in y2106 */
-	body_len = parsed[6];
-	major = parsed[7];
-	minor = parsed[8];
-	rdev = new_encode_dev(MKDEV(parsed[9], parsed[10]));
-	name_len = parsed[11];
-	hdr_csum = parsed[12];
-}
-
-/* FSM */
-
-static __initdata enum state {
-	Start,
-	Collect,
-	GotHeader,
-	SkipIt,
-	GotName,
-	CopyFile,
-	GotSymlink,
-	Reset
-} state, next_state;
-
-static __initdata char *victim;
-static unsigned long byte_count __initdata;
-static __initdata loff_t this_header, next_header;
-
-static inline void __init eat(unsigned n)
-{
-	victim += n;
-	this_header += n;
-	byte_count -= n;
-}
-
-static __initdata char *collected;
-static long remains __initdata;
-static __initdata char *collect;
-
-static void __init read_into(char *buf, unsigned size, enum state next)
-{
-	if (byte_count >= size) {
-		collected = victim;
-		eat(size);
-		state = next;
-	} else {
-		collect = collected = buf;
-		remains = size;
-		next_state = next;
-		state = Collect;
-	}
-}
-
-static __initdata char *header_buf, *symlink_buf, *name_buf;
-
-static int __init do_start(void)
-{
-	read_into(header_buf, 110, GotHeader);
-	return 0;
-}
-
-static int __init do_collect(void)
-{
-	unsigned long n = remains;
-	if (byte_count < n)
-		n = byte_count;
-	memcpy(collect, victim, n);
-	eat(n);
-	collect += n;
-	if ((remains -= n) != 0)
-		return 1;
-	state = next_state;
-	return 0;
-}
-
-static int __init do_header(void)
-{
-	if (!memcmp(collected, "070701", 6)) {
-		csum_present = false;
-	} else if (!memcmp(collected, "070702", 6)) {
-		csum_present = true;
-	} else {
-		if (memcmp(collected, "070707", 6) == 0)
-			error("incorrect cpio method used: use -H newc option");
-		else
-			error("no cpio magic");
-		return 1;
-	}
-	parse_header(collected);
-	next_header = this_header + N_ALIGN(name_len) + body_len;
-	next_header = (next_header + 3) & ~3;
-	state = SkipIt;
-	if (name_len <= 0 || name_len > PATH_MAX)
-		return 0;
-	if (S_ISLNK(mode)) {
-		if (body_len > PATH_MAX)
-			return 0;
-		collect = collected = symlink_buf;
-		remains = N_ALIGN(name_len) + body_len;
-		next_state = GotSymlink;
-		state = Collect;
-		return 0;
-	}
-	if (S_ISREG(mode) || !body_len)
-		read_into(name_buf, N_ALIGN(name_len), GotName);
-	return 0;
-}
-
-static int __init do_skip(void)
-{
-	if (this_header + byte_count < next_header) {
-		eat(byte_count);
-		return 1;
-	} else {
-		eat(next_header - this_header);
-		state = next_state;
-		return 0;
-	}
-}
-
-static int __init do_reset(void)
-{
-	while (byte_count && *victim == '\0')
-		eat(1);
-	if (byte_count && (this_header & 3))
-		error("broken padding");
-	return 1;
-}
-
-static void __init clean_path(char *path, umode_t fmode)
-{
-	struct kstat st;
-
-	if (!init_stat(path, &st, AT_SYMLINK_NOFOLLOW) &&
-	    (st.mode ^ fmode) & S_IFMT) {
-		if (S_ISDIR(st.mode))
-			init_rmdir(path);
-		else
-			init_unlink(path);
-	}
-}
-
-static int __init maybe_link(void)
-{
-	if (nlink >= 2) {
-		char *old = find_link(major, minor, ino, mode, collected);
-		if (old) {
-			clean_path(collected, 0);
-			return (init_link(old, collected) < 0) ? -1 : 1;
-		}
-	}
-	return 0;
-}
-
-static __initdata struct file *wfile;
-static __initdata loff_t wfile_pos;
-
-static int __init do_name(void)
-{
-	state = SkipIt;
-	next_state = Reset;
-	if (strcmp(collected, "TRAILER!!!") == 0) {
-		free_hash();
-		return 0;
-	}
-	clean_path(collected, mode);
-	if (S_ISREG(mode)) {
-		int ml = maybe_link();
-		if (ml >= 0) {
-			int openflags = O_WRONLY|O_CREAT;
-			if (ml != 1)
-				openflags |= O_TRUNC;
-			wfile = filp_open(collected, openflags, mode);
-			if (IS_ERR(wfile))
-				return 0;
-			wfile_pos = 0;
-			io_csum = 0;
-
-			vfs_fchown(wfile, uid, gid);
-			vfs_fchmod(wfile, mode);
-			if (body_len)
-				vfs_truncate(&wfile->f_path, body_len);
-			state = CopyFile;
-		}
-	} else if (S_ISDIR(mode)) {
-		init_mkdir(collected, mode);
-		init_chown(collected, uid, gid, 0);
-		init_chmod(collected, mode);
-		dir_add(collected, mtime);
-	} else if (S_ISBLK(mode) || S_ISCHR(mode) ||
-		   S_ISFIFO(mode) || S_ISSOCK(mode)) {
-		if (maybe_link() == 0) {
-			init_mknod(collected, mode, rdev);
-			init_chown(collected, uid, gid, 0);
-			init_chmod(collected, mode);
-			do_utime(collected, mtime);
-		}
-	}
-	return 0;
-}
+static unsigned long my_inptr; /* index of next byte to be processed in inbuf */
 
-static int __init do_copy(void)
-{
-	if (byte_count >= body_len) {
-		if (xwrite(wfile, victim, body_len, &wfile_pos) != body_len)
-			error("write error");
-
-		do_utime_path(&wfile->f_path, mtime);
-		fput(wfile);
-		if (csum_present && io_csum != hdr_csum)
-			error("bad data checksum");
-		eat(body_len);
-		state = SkipIt;
-		return 0;
-	} else {
-		if (xwrite(wfile, victim, byte_count, &wfile_pos) != byte_count)
-			error("write error");
-		body_len -= byte_count;
-		eat(byte_count);
-		return 1;
-	}
-}
+#include <linux/decompress/generic.h>
 
-static int __init do_symlink(void)
-{
-	collected[N_ALIGN(name_len) + body_len] = '\0';
-	clean_path(collected, 0);
-	init_symlink(collected + N_ALIGN(name_len), collected);
-	init_chown(collected, uid, gid, AT_SYMLINK_NOFOLLOW);
-	do_utime(collected, mtime);
-	state = SkipIt;
-	next_state = Reset;
-	return 0;
-}
+static struct cpio_context ctx __initdata;
 
-static __initdata int (*actions[])(void) = {
-	[Start]		= do_start,
-	[Collect]	= do_collect,
-	[GotHeader]	= do_header,
-	[SkipIt]	= do_skip,
-	[GotName]	= do_name,
-	[CopyFile]	= do_copy,
-	[GotSymlink]	= do_symlink,
-	[Reset]		= do_reset,
-};
-
-static long __init write_buffer(char *buf, unsigned long len)
+static long __init process_buffer(void *bufv, unsigned long len)
 {
-	byte_count = len;
-	victim = buf;
-
-	while (!actions[state]())
-		;
-	return len - byte_count;
+	return cpio_process_buffer(&ctx, bufv, len);
 }
 
-static long __init flush_buffer(void *bufv, unsigned long len)
-{
-	char *buf = (char *) bufv;
-	long written;
-	long origLen = len;
-	if (message)
-		return -1;
-	while ((written = write_buffer(buf, len)) < len && !message) {
-		char c = buf[written];
-		if (c == '0') {
-			buf += written;
-			len -= written;
-			state = Start;
-		} else if (c == 0) {
-			buf += written;
-			len -= written;
-			state = Reset;
-		} else
-			error("junk within compressed archive");
-	}
-	return origLen;
-}
-
-static unsigned long my_inptr; /* index of next byte to be processed in inbuf */
-
-#include <linux/decompress/generic.h>
-
 static char * __init unpack_to_rootfs(char *buf, unsigned long len)
 {
 	long written;
@@ -493,36 +52,34 @@  static char * __init unpack_to_rootfs(char *buf, unsigned long len)
 	const char *compress_name;
 	static __initdata char msg_buf[64];
 
-	header_buf = kmalloc(110, GFP_KERNEL);
-	symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
-	name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
-
-	if (!header_buf || !symlink_buf || !name_buf)
+	if (cpio_start(&ctx))
 		panic_show_mem("can't allocate buffers");
 
-	state = Start;
-	this_header = 0;
 	message = NULL;
 	while (!message && len) {
-		loff_t saved_offset = this_header;
-		if (*buf == '0' && !(this_header & 3)) {
-			state = Start;
-			written = write_buffer(buf, len);
+		loff_t saved_offset = ctx.this_header;
+
+		if (*buf == '0' && !(ctx.this_header & 3)) {
+			ctx.state = CPIO_START;
+			written = cpio_write_buffer(&ctx, buf, len);
 			buf += written;
 			len -= written;
+			if (ctx.errmsg)
+				message = ctx.errmsg;
 			continue;
 		}
 		if (!*buf) {
 			buf++;
 			len--;
-			this_header++;
+			ctx.this_header++;
 			continue;
 		}
-		this_header = 0;
+		ctx.this_header = 0;
 		decompress = decompress_method(buf, len, &compress_name);
 		pr_debug("Detected %s compressed data\n", compress_name);
 		if (decompress) {
-			int res = decompress(buf, len, NULL, flush_buffer, NULL,
+			int res = decompress(buf, len, NULL,
+				   process_buffer, NULL,
 				   &my_inptr, error);
 			if (res)
 				error("decompressor failed");
@@ -535,16 +92,13 @@  static char * __init unpack_to_rootfs(char *buf, unsigned long len)
 			}
 		} else
 			error("invalid magic at start of compressed archive");
-		if (state != Reset)
+		if (ctx.state != CPIO_RESET)
 			error("junk at the end of compressed archive");
-		this_header = saved_offset + my_inptr;
+		ctx.this_header = saved_offset + my_inptr;
 		buf += my_inptr;
 		len -= my_inptr;
 	}
-	dir_utime();
-	kfree(name_buf);
-	kfree(symlink_buf);
-	kfree(header_buf);
+	cpio_finish(&ctx);
 	return message;
 }
 
@@ -672,9 +226,11 @@  static inline bool kexec_free_initrd(void)
 #ifdef CONFIG_BLK_DEV_RAM
 static void __init populate_initrd_image(char *err)
 {
-	ssize_t written;
 	struct file *file;
+	unsigned char *p;
+	ssize_t written;
 	loff_t pos = 0;
+	size_t count;
 
 	unpack_to_rootfs(__initramfs_start, __initramfs_size);
 
@@ -684,11 +240,27 @@  static void __init populate_initrd_image(char *err)
 	if (IS_ERR(file))
 		return;
 
-	written = xwrite(file, (char *)initrd_start, initrd_end - initrd_start,
-			&pos);
-	if (written != initrd_end - initrd_start)
-		pr_err("/initrd.image: incomplete write (%zd != %ld)\n",
-		       written, initrd_end - initrd_start);
+	count = initrd_end - initrd_start;
+	p = (char *)initrd_start;
+	while (count) {
+		written = kernel_write(file, p, count, &pos);
+
+		if (written < 0) {
+			if (written == -EINTR || written == -EAGAIN)
+				continue;
+			break;
+		} else if (written == 0) {
+			break;
+		}
+
+		p += written;
+		count -= written;
+	}
+
+	if (count != 0)
+		pr_err("/initrd.image: incomplete write (%ld != %ld)\n",
+		       (initrd_end - initrd_start) - count,
+		       initrd_end - initrd_start);
 	fput(file);
 }
 #endif /* CONFIG_BLK_DEV_RAM */
diff --git a/lib/Makefile b/lib/Makefile
index f99bf61f8bbc..8db946deb71c 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -34,7 +34,7 @@  lib-y := ctype.o string.o vsprintf.o cmdline.o \
 	 is_single_threaded.o plist.o decompress.o kobject_uevent.o \
 	 earlycpio.o seq_buf.o siphash.o dec_and_lock.o \
 	 nmi_backtrace.o nodemask.o win_minmax.o memcat_p.o \
-	 buildid.o
+	 buildid.o cpio.o
 
 lib-$(CONFIG_PRINTK) += dump_stack.o
 lib-$(CONFIG_SMP) += cpumask.o
diff --git a/lib/cpio.c b/lib/cpio.c
new file mode 100644
index 000000000000..c71bebd4cc98
--- /dev/null
+++ b/lib/cpio.c
@@ -0,0 +1,454 @@ 
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/cpio.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/init_syscalls.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+static ssize_t __init xwrite(struct cpio_context *ctx, struct file *file,
+			     const unsigned char *p, size_t count, loff_t *pos)
+{
+	ssize_t out = 0;
+
+	/* sys_write only can write MAX_RW_COUNT aka 2G-4K bytes at most */
+	while (count) {
+		ssize_t rv = kernel_write(file, p, count, pos);
+
+		if (rv < 0) {
+			if (rv == -EINTR || rv == -EAGAIN)
+				continue;
+			return out ? out : rv;
+		} else if (rv == 0) {
+			break;
+		}
+
+		if (ctx->csum_present) {
+			ssize_t i;
+
+			for (i = 0; i < rv; i++)
+				ctx->io_csum += p[i];
+		}
+
+		p += rv;
+		out += rv;
+		count -= rv;
+	}
+
+	return out;
+}
+
+/* link hash */
+
+static inline int hash(int major, int minor, int ino)
+{
+	unsigned long tmp = ino + minor + (major << 3);
+
+	tmp += tmp >> 5;
+	return tmp & (CPIO_LINK_HASH_SIZE - 1);
+}
+
+static char __init *find_link(struct cpio_context *ctx, int major, int minor,
+			      int ino, umode_t mode, char *name)
+{
+	struct cpio_link_hash **p, *q;
+
+	for (p = ctx->link_hash + hash(major, minor, ino); *p; p = &(*p)->next) {
+		if ((*p)->ino != ino)
+			continue;
+		if ((*p)->minor != minor)
+			continue;
+		if ((*p)->major != major)
+			continue;
+		if (((*p)->mode ^ mode) & S_IFMT)
+			continue;
+		return (*p)->name;
+	}
+	q = kmalloc(sizeof(*q), GFP_KERNEL);
+	if (!q)
+		return ERR_PTR(-ENOMEM);
+	q->major = major;
+	q->minor = minor;
+	q->ino = ino;
+	q->mode = mode;
+	strcpy(q->name, name);
+	q->next = NULL;
+	*p = q;
+	return NULL;
+}
+
+static void __init free_hash(struct cpio_context *ctx)
+{
+	struct cpio_link_hash **p, *q;
+
+	for (p = ctx->link_hash; p < ctx->link_hash + CPIO_LINK_HASH_SIZE; p++) {
+		while (*p) {
+			q = *p;
+			*p = q->next;
+			kfree(q);
+		}
+	}
+}
+
+#ifdef CONFIG_INITRAMFS_PRESERVE_MTIME
+static void __init do_utime(char *filename, time64_t mtime)
+{
+	struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
+
+	init_utimes(filename, t);
+}
+
+static void __init do_utime_path(const struct path *path, time64_t mtime)
+{
+	struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
+
+	vfs_utimes(path, t);
+}
+
+static int __init dir_add(struct cpio_context *ctx, const char *name, time64_t mtime)
+{
+	size_t nlen = strlen(name) + 1;
+	struct cpio_dir_entry *de;
+
+	de = kmalloc(sizeof(*de) + nlen, GFP_KERNEL);
+	if (!de)
+		return -ENOMEM;
+	INIT_LIST_HEAD(&de->list);
+	strscpy(de->name, name, nlen);
+	de->mtime = mtime;
+	list_add(&de->list, &ctx->dir_list);
+
+	return 0;
+}
+
+static void __init dir_utime(struct cpio_context *ctx)
+{
+	struct cpio_dir_entry *de, *tmp;
+
+	list_for_each_entry_safe(de, tmp, &ctx->dir_list, list) {
+		list_del(&de->list);
+		do_utime(de->name, de->mtime);
+		kfree(de);
+	}
+}
+#else
+static void __init do_utime(char *filename, time64_t mtime) {}
+static void __init do_utime_path(const struct path *path, time64_t mtime) {}
+static int __init dir_add(struct cpio_context *ctx, const char *name, time64_t mtime) { return 0; }
+static void __init dir_utime(struct cpio_context *ctx) {}
+#endif
+
+/* cpio header parsing */
+
+static void __init parse_header(struct cpio_context *ctx, char *s)
+{
+	unsigned long parsed[13];
+	char buf[9];
+	int i;
+
+	buf[8] = '\0';
+	for (i = 0, s += 6; i < 13; i++, s += 8) {
+		memcpy(buf, s, 8);
+		parsed[i] = simple_strtoul(buf, NULL, 16);
+	}
+	ctx->ino = parsed[0];
+	ctx->mode = parsed[1];
+	ctx->uid = parsed[2];
+	ctx->gid = parsed[3];
+	ctx->nlink = parsed[4];
+	ctx->mtime = parsed[5]; /* breaks in y2106 */
+	ctx->body_len = parsed[6];
+	ctx->major = parsed[7];
+	ctx->minor = parsed[8];
+	ctx->rdev = new_encode_dev(MKDEV(parsed[9], parsed[10]));
+	ctx->name_len = parsed[11];
+	ctx->hdr_csum = parsed[12];
+}
+
+/* FSM */
+
+static inline void __init eat(struct cpio_context *ctx, unsigned int n)
+{
+	ctx->victim += n;
+	ctx->this_header += n;
+	ctx->byte_count -= n;
+}
+
+static void __init read_into(struct cpio_context *ctx, char *buf,
+			     unsigned int size, enum cpio_state next)
+{
+	if (ctx->byte_count >= size) {
+		ctx->collected = ctx->victim;
+		eat(ctx, size);
+		ctx->state = next;
+	} else {
+		ctx->collect = buf;
+		ctx->collected = buf;
+		ctx->remains = size;
+		ctx->next_state = next;
+		ctx->state = CPIO_COLLECT;
+	}
+}
+
+static int __init do_start(struct cpio_context *ctx)
+{
+	read_into(ctx, ctx->header_buf, 110, CPIO_GOTHEADER);
+	return 0;
+}
+
+static int __init do_collect(struct cpio_context *ctx)
+{
+	unsigned long n = ctx->remains;
+
+	if (ctx->byte_count < n)
+		n = ctx->byte_count;
+	memcpy(ctx->collect, ctx->victim, n);
+	eat(ctx, n);
+	ctx->collect += n;
+	ctx->remains -= n;
+
+	if (ctx->remains != 0)
+		return 1;
+
+	ctx->state = ctx->next_state;
+	return 0;
+}
+
+static int __init do_header(struct cpio_context *ctx)
+{
+	if (!memcmp(ctx->collected, "070701", 6)) {
+		ctx->csum_present = false;
+	} else if (!memcmp(ctx->collected, "070702", 6)) {
+		ctx->csum_present = true;
+	} else {
+		if (memcmp(ctx->collected, "070707", 6) == 0)
+			ctx->errmsg = "incorrect cpio method used: use -H newc option";
+		else
+			ctx->errmsg = "no cpio magic";
+		return 1;
+	}
+	parse_header(ctx, ctx->collected);
+	ctx->next_header = ctx->this_header + N_ALIGN(ctx->name_len) + ctx->body_len;
+	ctx->next_header = (ctx->next_header + 3) & ~3;
+	ctx->state = CPIO_SKIPIT;
+	if (ctx->name_len <= 0 || ctx->name_len > PATH_MAX)
+		return 0;
+	if (S_ISLNK(ctx->mode)) {
+		if (ctx->body_len > PATH_MAX)
+			return 0;
+		ctx->collect = ctx->symlink_buf;
+		ctx->collected = ctx->symlink_buf;
+		ctx->remains = N_ALIGN(ctx->name_len) + ctx->body_len;
+		ctx->next_state = CPIO_GOTSYMLINK;
+		ctx->state = CPIO_COLLECT;
+		return 0;
+	}
+	if (S_ISREG(ctx->mode) || !ctx->body_len)
+		read_into(ctx, ctx->name_buf, N_ALIGN(ctx->name_len), CPIO_GOTNAME);
+	return 0;
+}
+
+static int __init do_skip(struct cpio_context *ctx)
+{
+	if (ctx->this_header + ctx->byte_count < ctx->next_header) {
+		eat(ctx, ctx->byte_count);
+		return 1;
+	}
+
+	eat(ctx, ctx->next_header - ctx->this_header);
+	ctx->state = ctx->next_state;
+	return 0;
+}
+
+static int __init do_reset(struct cpio_context *ctx)
+{
+	while (ctx->byte_count && *ctx->victim == '\0')
+		eat(ctx, 1);
+	if (ctx->byte_count && (ctx->this_header & 3))
+		ctx->errmsg = "broken padding";
+	return 1;
+}
+
+static void __init clean_path(char *path, umode_t fmode)
+{
+	struct kstat st;
+
+	if (!init_stat(path, &st, AT_SYMLINK_NOFOLLOW) &&
+	    (st.mode ^ fmode) & S_IFMT) {
+		if (S_ISDIR(st.mode))
+			init_rmdir(path);
+		else
+			init_unlink(path);
+	}
+}
+
+static int __init maybe_link(struct cpio_context *ctx)
+{
+	if (ctx->nlink >= 2) {
+		char *old = find_link(ctx, ctx->major, ctx->minor, ctx->ino,
+				ctx->mode, ctx->collected);
+		if (old) {
+			clean_path(ctx->collected, 0);
+			return (init_link(old, ctx->collected) < 0) ? -1 : 1;
+		}
+	}
+	return 0;
+}
+
+static int __init do_name(struct cpio_context *ctx)
+{
+	ctx->state = CPIO_SKIPIT;
+	ctx->next_state = CPIO_RESET;
+	if (strcmp(ctx->collected, "TRAILER!!!") == 0) {
+		free_hash(ctx);
+		return 0;
+	}
+	clean_path(ctx->collected, ctx->mode);
+	if (S_ISREG(ctx->mode)) {
+		int ml = maybe_link(ctx);
+
+		if (ml >= 0) {
+			int openflags = O_WRONLY | O_CREAT;
+
+			if (ml != 1)
+				openflags |= O_TRUNC;
+			ctx->wfile = filp_open(ctx->collected, openflags, ctx->mode);
+			if (IS_ERR(ctx->wfile))
+				return 0;
+			ctx->wfile_pos = 0;
+			ctx->io_csum = 0;
+
+			vfs_fchown(ctx->wfile, ctx->uid, ctx->gid);
+			vfs_fchmod(ctx->wfile, ctx->mode);
+			if (ctx->body_len)
+				vfs_truncate(&ctx->wfile->f_path, ctx->body_len);
+			ctx->state = CPIO_COPYFILE;
+		}
+	} else if (S_ISDIR(ctx->mode)) {
+		init_mkdir(ctx->collected, ctx->mode);
+		init_chown(ctx->collected, ctx->uid, ctx->gid, 0);
+		init_chmod(ctx->collected, ctx->mode);
+		dir_add(ctx, ctx->collected, ctx->mtime);
+	} else if (S_ISBLK(ctx->mode) || S_ISCHR(ctx->mode) ||
+		   S_ISFIFO(ctx->mode) || S_ISSOCK(ctx->mode)) {
+		if (maybe_link(ctx) == 0) {
+			init_mknod(ctx->collected, ctx->mode, ctx->rdev);
+			init_chown(ctx->collected, ctx->uid, ctx->gid, 0);
+			init_chmod(ctx->collected, ctx->mode);
+			do_utime(ctx->collected, ctx->mtime);
+		}
+	}
+	return 0;
+}
+
+static int __init do_copy(struct cpio_context *ctx)
+{
+	if (ctx->byte_count >= ctx->body_len) {
+		if (xwrite(ctx, ctx->wfile, ctx->victim, ctx->body_len,
+			   &ctx->wfile_pos) != ctx->body_len)
+			ctx->errmsg = "write error";
+
+		do_utime_path(&ctx->wfile->f_path, ctx->mtime);
+		fput(ctx->wfile);
+		if (ctx->csum_present && ctx->io_csum != ctx->hdr_csum)
+			ctx->errmsg = "bad data checksum";
+		eat(ctx, ctx->body_len);
+		ctx->state = CPIO_SKIPIT;
+		return 0;
+	}
+
+	if (xwrite(ctx, ctx->wfile, ctx->victim, ctx->byte_count,
+		   &ctx->wfile_pos) != ctx->byte_count)
+		ctx->errmsg = "write error";
+	ctx->body_len -= ctx->byte_count;
+	eat(ctx, ctx->byte_count);
+	return 1;
+}
+
+static int __init do_symlink(struct cpio_context *ctx)
+{
+	ctx->collected[N_ALIGN(ctx->name_len) + ctx->body_len] = '\0';
+	clean_path(ctx->collected, 0);
+	init_symlink(ctx->collected + N_ALIGN(ctx->name_len), ctx->collected);
+	init_chown(ctx->collected, ctx->uid, ctx->gid, AT_SYMLINK_NOFOLLOW);
+	do_utime(ctx->collected, ctx->mtime);
+	ctx->state = CPIO_SKIPIT;
+	ctx->next_state = CPIO_RESET;
+	return 0;
+}
+
+static __initdata int (*actions[])(struct cpio_context *) = {
+	[CPIO_START]		= do_start,
+	[CPIO_COLLECT]		= do_collect,
+	[CPIO_GOTHEADER]	= do_header,
+	[CPIO_SKIPIT]		= do_skip,
+	[CPIO_GOTNAME]		= do_name,
+	[CPIO_COPYFILE]		= do_copy,
+	[CPIO_GOTSYMLINK]	= do_symlink,
+	[CPIO_RESET]		= do_reset,
+};
+
+long __init cpio_write_buffer(struct cpio_context *ctx, char *buf,
+			      unsigned long len)
+{
+	ctx->byte_count = len;
+	ctx->victim = buf;
+
+	while (!actions[ctx->state](ctx))
+		;
+	return len - ctx->byte_count;
+}
+
+long __init cpio_process_buffer(struct cpio_context *ctx, void *bufv,
+				unsigned long len)
+{
+	char *buf = (char *)bufv;
+	long written;
+	long left = len;
+
+	if (ctx->errmsg)
+		return -1;
+
+	while ((written = cpio_write_buffer(ctx, buf, left)) < left && !ctx->errmsg) {
+		char c = buf[written];
+
+		if (c == '0') {
+			buf += written;
+			left -= written;
+			ctx->state = CPIO_START;
+		} else if (c == 0) {
+			buf += written;
+			left -= written;
+			ctx->state = CPIO_RESET;
+		} else {
+			ctx->errmsg = "junk within compressed archive";
+		}
+	}
+
+	return len;
+}
+
+int __init cpio_start(struct cpio_context *ctx)
+{
+	ctx->header_buf = kmalloc(110, GFP_KERNEL);
+	ctx->symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
+	ctx->name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
+
+	if (!ctx->header_buf || !ctx->symlink_buf || !ctx->name_buf)
+		return -ENOMEM;
+
+	ctx->state = CPIO_START;
+	ctx->this_header = 0;
+	INIT_LIST_HEAD(&ctx->dir_list);
+
+	return 0;
+}
+
+void __init cpio_finish(struct cpio_context *ctx)
+{
+	dir_utime(ctx);
+	kfree(ctx->name_buf);
+	kfree(ctx->symlink_buf);
+	kfree(ctx->header_buf);
+}