779 lines · 17.9 KB · C
RawDownload
// 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>#include <linux/delay.h>#include <linux/string.h>#include <linux/dirent.h>#include <linux/syscalls.h>#include <linux/utime.h>#include <linux/file.h>#include <linux/kstrtox.h>#include <linux/memblock.h>#include <linux/mm.h>#include <linux/namei.h>#include <linux/init_syscalls.h>#include <linux/umh.h>#include <linux/security.h> #include "do_mounts.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;} static __initdata char *message;static void __init error(char *x){	if (!message)		message = x;} #define panic_show_mem(fmt, ...) \	({ show_mem(); panic(fmt, ##__VA_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_MTIMEstatic 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);	}}#elsestatic 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; 	/* name_len > 0 && name_len <= PATH_MAX checked in do_header */	if (collected[name_len - 1] != '\0') {		pr_err("initramfs name without nulterm: %.*s\n",		       (int)name_len, collected);		error("malformed archive");		return 1;	} 	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|O_LARGEFILE;			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 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;	}} static int __init do_symlink(void){	if (collected[name_len - 1] != '\0') {		pr_err("initramfs symlink without nulterm: %.*s\n",		       (int)name_len, collected);		error("malformed archive");		return 1;	}	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 __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){	byte_count = len;	victim = buf; 	while (!actions[state]())		;	return len - byte_count;} static long __init flush_buffer(void *bufv, unsigned long len){	char *buf = 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 __initdata; /* 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)

Lines 1–500 of 779.