android-bootimage 0.1.0

A tool to handle android boot images. It currently only works for Samsung boot images.
Documentation
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
extern crate android_bootimage;
#[macro_use]
extern crate clap;
extern crate termcolor;
extern crate humansize;

use android_bootimage::{MagicHeader, Section};
use clap::{App, Arg, ArgMatches};
use console::ConsoleOutputHandler;
use std::io::{Read, Seek, Write};
use std::path::Path;
use termcolor::ColorChoice;

const ARG_UNPACK_ALL_LONG_HELP: &'static str = "
Unpack all sections of the boot image to their default locations. 

The default locations for the different sections are 'boot/SECTION_NAME.img', where 'SECTION_NAME' is the name of the section. For example, the kernel will be put into 'boot/kernel.img'.
";

fn main() {
    let console = ConsoleOutputHandler::new(ColorChoice::Auto);

    match create_app().get_matches().subcommand() {
        ("unpack", Some(arguments)) => main_unpack(arguments, console),
        ("sections", Some(arguments)) => main_sections(arguments, console),
        _ => unreachable!(),
    }
}

fn create_app() -> App<'static, 'static> {
    use clap::AppSettings;
    App::new("android-bootimage")
        .setting(AppSettings::SubcommandRequired)
        .version(crate_version!())
        .author(crate_authors!())
        .about("Program for handling samsung boot images.")
        .subcommand(create_app_unpack())
        .subcommand(create_app_sections())
}

fn create_app_unpack() -> App<'static, 'static> {
    App::new("unpack")
        .about("Unpacks samsung boot images.")
        .arg(
            Arg::with_name("input_file")
                .required(true)
                .help("The boot image, for example 'boot.img'")
                .value_name("INPUT_FILE"),
        )
        .arg(
            Arg::with_name("unpack_all")
                .long("unpack-all")
                .short("a")
                .help(
                    "Unpack all sections of the boot image to their default locations",
                )
                .long_help(ARG_UNPACK_ALL_LONG_HELP),
        )
        .arg(
            Arg::with_name("output_kernel_file")
                .long("kernel")
                .help("The file to extract the kernel into")
                .long_help(
                    "The file to extract the kernel into. If this file already exists \
                     it will be emptied first.",
                )
                .value_name("OUTPUT_KERNEL_FILE")
                .default_value_if("unpack_all", None, "boot/kernel.img"),
        )
        .arg(
            Arg::with_name("output_ramdisk_file")
                .long("ramdisk")
                .help("The file to extract the ramdisk into")
                .long_help(
                    "The file to extract the ramdisk into. If this file already exists \
                     it will be emptied first.",
                )
                .value_name("OUTPUT_RAMDISK_FILE")
                .default_value_if("unpack_all", None, "boot/ramdisk.img"),
        )
        .arg(
            Arg::with_name("output_second_file")
                .long("second")
                .help("The file to extract the optional second file into")
                .long_help(
                    "The file to extract the optional second file into. If this file already \
                     exists it will be emptied first.",
                )
                .value_name("OUTPUT_SECOND_FILE")
                .default_value_if("unpack_all", None, "boot/second.img"),
        )
        .arg(
            Arg::with_name("output_tree_file")
                .long("tree")
                .help("The file to extract the device tree file into")
                .long_help(
                    "The file to extract the device tree file into. If this file already \
                     exists it will be emptied first.",
                )
                .value_name("OUTPUT_TREE_FILE")
                .default_value_if("unpack_all", None, "boot/device_tree.img"),
        )
        .arg(
            Arg::with_name("no_magic_check")
                .help("Do not check if the magic signature is correct")
                .long("--no-magic-check"),
        )
        .arg(
            Arg::with_name("page_size")
                .short("p")
                .long("page-size")
                .help("Use a custom page size")
                .long_help(
                    "Use a custom page size. This may be required on some boot images.",
                )
                .value_name("PAGE_SIZE"),
        )
}

fn create_app_sections() -> App<'static, 'static> {
    App::new("sections")
        .about("Lists the sections in a boot image.")
        .arg(
            Arg::with_name("input_file")
                .required(true)
                .help("The boot image, for example 'boot.img'")
                .value_name("INPUT_FILE"),
        )
        .arg(
            Arg::with_name("no_magic_check")
                .help("Do not check if the magic signature is correct")
                .long("--no-magic-check"),
        )
        .arg(
            Arg::with_name("page_size")
                .short("p")
                .long("page-size")
                .help("Use a custom page size")
                .long_help(
                    "Use a custom page size. This may be required on some boot images.",
                )
                .value_name("PAGE_SIZE"),
        )
}

fn main_unpack(arguments: &ArgMatches, mut console: ConsoleOutputHandler) {
    use std::fs::File;

    let input_path = Path::new(arguments.value_of("input_file").unwrap());

    let mut input_file = match File::open(input_path) {
        Ok(file) => file,
        Err(error) => {
            console.print_fatal_error(
                &format!("to open boot image file '{}'", input_path.display()),
                Some(&error),
            )
        }
    };

    let header = read_header(
        &mut input_file,
        !arguments.is_present("no_magic_check"),
        arguments.value_of("page_size").map(|_| {
            value_t!(arguments.value_of("page_size"), u32).unwrap_or_else(|error| error.exit())
        }),
        &mut console,
    );

    console.print_status_success("Parsed", "header.");

    let copy_requested = {
        // Helper function to extract sections out of the boot image. Returns true if
        // the user requested to extract this section, false otherwise.
        let mut copy_data = |output_key, section| if let Some(output_path) =
            arguments.value_of(output_key).map(|path| Path::new(path))
        {
            let data = match header.read_section_from(&mut input_file, section) {
                Ok(data) => data,
                Err(error) => {
                    console.print_error_as_warning(
                        &format!(
                            "Failed to read '{}' section from boot image '{}'",
                            section,
                            input_path.display()
                        ),
                        Some(&error),
                    );
                    return true;
                }
            };

            {
                if let Some(parent_path) = output_path.parent() {
                    if !parent_path.exists() {
                        use std::fs::create_dir_all;
                        if let Err(ref error) = create_dir_all(parent_path) {
                            console.print_error_as_warning(
                                &format!(
                                    "Could not create '{}' directory. ",
                                    parent_path.display()
                                ),
                                Some(error),
                            )
                        } else {
                            console.print_status_success(
                                "Created",
                                &format!("directory '{}'.", parent_path.display()),
                            )
                        }
                    }
                }
            }

            match File::create(output_path).and_then(|mut file| file.write_all(&data)) {
                Ok(_) => {
                    console.print_status_success(
                        "Unpacked",
                        &format!("'{}' section into '{}'.", section, output_path.display()),
                    )
                }
                Err(error) => {
                    console.print_error_as_warning(
                        &format!(
                            "Failed to write '{}' section into '{}'",
                            section,
                            output_path.display()
                        ),
                        Some(&error),
                    )
                }
            }

            return true;
        } else {
            return false;
        };

        [
            copy_data("output_kernel_file", Section::Kernel),
            copy_data("output_ramdisk_file", Section::Ramdisk),
            copy_data("output_second_file", Section::Second),
            copy_data("output_tree_file", Section::DeviceTree),
        ].iter()
            .any(|&copy_requested| copy_requested)
    };

    if !copy_requested {
        console.print_warning_message(
            "No sections extracted, as no sections were requested to be extracted.",
        )
    }
}

fn main_sections(arguments: &ArgMatches, mut console: ConsoleOutputHandler) {
    use std::fs::File;

    let input_path = Path::new(arguments.value_of("input_file").unwrap());

    let mut input_file = match File::open(input_path) {
        Ok(file) => file,
        Err(error) => {
            console.print_fatal_error(
                &format!("to open boot image file '{}'", input_path.display()),
                Some(&error),
            )
        }
    };

    let header = read_header(
        &mut input_file,
        !arguments.is_present("no_magic_check"),
        arguments.value_of("page_size").map(|_| {
            value_t!(arguments.value_of("page_size"), u32).unwrap_or_else(|error| error.exit())
        }),
        &mut console,
    );

    for section in header.sections() {
        use humansize::FileSize;
        use humansize::file_size_opts::BINARY as BINARY_FILE_SIZE;

        match header.section_location(section) {
            Ok((start, size)) => {
                println!(
                    "0x{:08X} - {: <12} (size: {})",
                    start,
                    section,
                    size.file_size(BINARY_FILE_SIZE).unwrap()
                );
            }
            Err(ref error) => {
                println!("0x???????? - {: <12} (size: ?)", section);
                console.print_error_as_warning(
                    &format!("Could not get loction of '{}' section.", section),
                    Some(error),
                );
            }
        }
    }
}

fn read_header<R: Read + Seek>(
    source: &mut R,
    magic_check: bool,
    override_page_size: Option<u32>,
    console: &mut ConsoleOutputHandler,
) -> MagicHeader {
    if !magic_check {
        // Make clear that any following errors might be caused by it not being a valid
        // header.
        console.print_warning_message("Skipping header magic check.");
    }

    let mut header = match MagicHeader::read_from(source, magic_check) {
        Ok(header) => header,
        Err(error) => console.print_fatal_error("Failed to parse boot image header", Some(&error)),
    };

    if let Some(page_size) = override_page_size {
        header.page_size = page_size;
    }

    header
}

mod console {
    use std::error::Error;
    use std::io::Write;
    use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

    /// An interface for the application to the console output. Handles things
    /// like
    /// formatting.
    ///
    /// If this structure ever fails writing, the error will be silently
    /// ignored.
    pub struct ConsoleOutputHandler {
        stream: StandardStream,
    }

    impl ConsoleOutputHandler {
        /// Creates a new structure.
        pub fn new(color: ColorChoice) -> Self {
            ConsoleOutputHandler { stream: StandardStream::stdout(color) }
        }

        pub fn print_message(&mut self, message: &str) {
            let _ = self.stream.set_color(&ColorSpec::new());
            let _ = writeln!(self.stream, "{}", message);
        }

        pub fn print_error_message(&mut self, message: &str) {
            let _ = self.stream.set_color(
                ColorSpec::new()
                    .set_fg(Some(Color::Red))
                    .set_bold(true),
            );

            let _ = write!(self.stream, "error: ");
            self.print_message(message);
        }

        pub fn print_warning_message(&mut self, message: &str) {
            let _ = self.stream.set_color(
                ColorSpec::new()
                    .set_fg(Some(Color::Yellow))
                    .set_bold(true),
            );

            let _ = write!(self.stream, "warning: ");
            self.print_message(message);
        }

        fn print_status(&mut self, colour: &ColorSpec, status: &str, message: &str) {
            let _ = self.stream.set_color(colour);
            let _ = write!(self.stream, "{: >12}", status);
            let _ = self.stream.set_color(&ColorSpec::new());
            let _ = writeln!(self.stream, " {}", message);
        }

        pub fn print_status_success(&mut self, status: &str, message: &str) {
            self.print_status(
                ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true),
                status,
                message,
            );
        }

        fn print_error_cause(&mut self, mut error_opt: Option<&Error>, colour: Color) {
            let _ = self.stream.set_color(
                ColorSpec::new().set_fg(Some(colour.clone())),
            );

            let colour_spec = {
                let mut colour_spec = ColorSpec::new();
                colour_spec.set_fg(Some(colour));
                colour_spec
            };

            while let Some(error) = error_opt {
                let _ = self.stream.set_color(&colour_spec);
                let _ = write!(self.stream, "caused by: ");
                self.print_message(&format!("{}", error));
                error_opt = error.cause();
            }
        }

        pub fn print_error_as_error(&mut self, message: &str, error_opt: Option<&Error>) {
            self.print_error_message(message);
            self.print_error_cause(error_opt, Color::Red);
        }

        pub fn print_error_as_warning(&mut self, message: &str, error_opt: Option<&Error>) {
            self.print_warning_message(message);
            self.print_error_cause(error_opt, Color::Yellow);
        }

        pub fn print_fatal_error(&mut self, message: &str, error_opt: Option<&Error>) -> ! {
            use std::process::exit;
            self.print_error_as_error(message, error_opt);
            exit(1);
        }
    }
}