summaryrefslogtreecommitdiffstats
path: root/unmkinitramfs
blob: 3f8c75d79cba62b7e47c3bbc1e003eaf05b7a361 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#!/bin/sh

set -eu

usage()
{
	cat << EOF

Usage: unmkinitramfs [-v] initramfs-file directory

Options:
  -v   Display verbose messages about extraction

See unmkinitramfs(8) for further details.

EOF
}

usage_error()
{
	usage >&2
	exit 2
}

# Extract a compressed cpio archive
xcpio()
{
	archive="$1"
	dir="$2"
	shift 2

	if gzip -t "$archive" >/dev/null 2>&1 ; then
		gzip -c -d "$archive"
	elif xzcat -t "$archive" >/dev/null 2>&1 ; then
		xzcat "$archive"
	elif lz4cat -t < "$archive" >/dev/null 2>&1 ; then
		lz4cat "$archive"
	elif bzip2 -t "$archive" >/dev/null 2>&1 ; then
		bzip2 -c -d "$archive"
	elif lzop -t "$archive" >/dev/null 2>&1 ; then
		lzop -c -d "$archive"
	# Ignoring other data, which may be garbage at the end of the file
	fi | (
		if [ -n "$dir" ]; then
			mkdir -p -- "$dir"
			cd -- "$dir"
		fi
		cpio "$@"
	)
}

# Read bytes out of a file, checking that they are valid hex digits
readhex()
{
	dd < "$1" bs=1 skip="$2" count="$3" 2> /dev/null | \
		LANG=C grep -E "^[0-9A-Fa-f]{$3}\$"
}

# Check for a zero byte in a file
checkzero()
{
	dd < "$1" bs=1 skip="$2" count=1 2> /dev/null | \
		LANG=C grep -q -z '^$'
}

# Split an initramfs into archives and call xcpio on each
splitinitramfs()
{
	initramfs="$1"
	dir="$2"
	shift 2

	count=0
	start=0
	while true; do
		# There may be prepended uncompressed archives.  cpio
		# won't tell us the true size of these so we have to
		# parse the headers and padding ourselves.  This is
		# very roughly based on linux/lib/earlycpio.c
		end=$start
		while true; do
			if checkzero "$initramfs" $end; then
				# This is the EOF marker.  There might
				# be more zero padding before the next
				# archive, so read through all of it.
				end=$((end + 4))
				while checkzero "$initramfs" $end; do
					end=$((end + 4))
				done
				break
			fi
			magic="$(readhex "$initramfs" $end 6)" || break
			test "$magic" = 070701 || test "$magic" = 070702 || break
			namesize=0x$(readhex "$initramfs" $((end + 94)) 8)
			filesize=0x$(readhex "$initramfs" $((end + 54)) 8)
			end=$(((end + 110)))
			end=$(((end + namesize + 3) & ~3))
			end=$(((end + filesize + 3) & ~3))
		done
		if [ $end -eq $start ]; then
			break
		fi

		# Extract to early, early2, ... subdirectories
		count=$((count + 1))
		if [ $count -eq 1 ]; then
			subdir=early
		else
			subdir=early$count
		fi
		dd < "$initramfs" skip=$start count=$((end - start)) iflag=skip_bytes 2> /dev/null |
		(
			if [ -n "$dir" ]; then
				mkdir -p -- "$dir/$subdir"
				cd -- "$dir/$subdir"
			fi
			cpio -i "$@"
		)
		start=$end
	done

	if [ $end -gt 0 ]; then
		# Extract to main subdirectory
		subarchive=$(mktemp "${TMPDIR:-/var/tmp}/unmkinitramfs_XXXXXX")
		trap 'rm -f "$subarchive"' EXIT
		dd < "$initramfs" skip=$end iflag=skip_bytes 2> /dev/null \
			> "$subarchive"
		xcpio "$subarchive" "${dir:+$dir/main}" -i "$@"
	else
		# Don't use subdirectories (for backward compatibility)
		xcpio "$initramfs" "$dir" -i "$@"
	fi
}

OPTIONS=$(getopt -o hv --long help,list,verbose -n "$0" -- "$@") || usage_error

cpio_opts="--preserve-modification-time --no-absolute-filenames --quiet"
expected_args=2
eval set -- "$OPTIONS"

while true; do
	case "$1" in
        -h|--help)
		usage
		exit 0
	;;
	--list)
		# For lsinitramfs
		cpio_opts="${cpio_opts:+${cpio_opts} --list}"
		expected_args=1
		shift
	;;
	-v|--verbose)
		cpio_opts="${cpio_opts:+${cpio_opts} --verbose}"
		shift
	;;
	--)
		shift
		break
	;;
	*)
		echo "Internal error!" >&2
		exit 1
	esac
done

if [ $# -ne $expected_args ]; then
	usage_error
fi

# shellcheck disable=SC2086
splitinitramfs "$1" "${2:-}" $cpio_opts