diff options
author | David Turner <digit@android.com> | 2010-10-28 09:57:37 -0700 |
---|---|---|
committer | Android Code Review <code-review@android.com> | 2010-10-28 09:57:37 -0700 |
commit | e6a439592e11235890fef5aa217c44f71f8fe45d (patch) | |
tree | aff76c935e32ce7bf6a0ae26150fa1ffd56e8faa | |
parent | c6d52d47a99a487c026e46f79e1c7b10feabcf08 (diff) | |
parent | ae835acc2d1a8b4aaeb599add9cfc02ea5789eaf (diff) | |
download | external_qemu-e6a439592e11235890fef5aa217c44f71f8fe45d.zip external_qemu-e6a439592e11235890fef5aa217c44f71f8fe45d.tar.gz external_qemu-e6a439592e11235890fef5aa217c44f71f8fe45d.tar.bz2 |
Merge "Add command line flag to show list of snapshots."
-rw-r--r-- | Makefile.android | 2 | ||||
-rw-r--r-- | android/cmdline-options.h | 1 | ||||
-rw-r--r-- | android/help.c | 15 | ||||
-rw-r--r-- | android/main.c | 12 | ||||
-rw-r--r-- | android/snapshot.c | 336 | ||||
-rw-r--r-- | android/snapshot.h | 31 |
6 files changed, 394 insertions, 3 deletions
diff --git a/Makefile.android b/Makefile.android index 1cc904a..1d82197 100644 --- a/Makefile.android +++ b/Makefile.android @@ -693,6 +693,7 @@ CORE_MISC_SOURCES = vl-android.c \ android/hw-qemud.c \ android/core-init-utils.c \ android/config.c \ + android/snapshot.c \ ifeq ($(HOST_ARCH),x86) CORE_MISC_SOURCES += i386-dis.c @@ -1181,6 +1182,7 @@ VL_SOURCES := framebuffer.c \ android/cmdline-option.c \ android/config.c \ android/display.c \ + android/snapshot.c \ android/main.c \ qemu-timer-ui.c \ vl-android-ui.c \ diff --git a/android/cmdline-options.h b/android/cmdline-options.h index a9b02ac..da8f375 100644 --- a/android/cmdline-options.h +++ b/android/cmdline-options.h @@ -81,6 +81,7 @@ OPT_PARAM( snapstorage, "<file>", "file that contains all state snapshots (de OPT_FLAG ( no_snapstorage, "do not mount a snapshot storage file (this disables all snapshot functionality)" ) OPT_PARAM( snapshot, "<name>", "immediately load state snapshot rather than doing a full boot (default 'default-boot')" ) OPT_FLAG ( no_snapshot, "do not start from snapshot, but perform a full boot sequence" ) +OPT_FLAG ( snapshot_list, "show a list of available snapshots" ) #endif OPT_FLAG ( wipe_data, "reset the use data image (copy it from initdata)" ) CFG_PARAM( avd, "<name>", "use a specific android virtual device" ) diff --git a/android/help.c b/android/help.c index 17e7a90..f589b3e 100644 --- a/android/help.c +++ b/android/help.c @@ -700,6 +700,21 @@ help_no_snapshot(stralloc_t* out) ); } +static void +help_snapshot_list(stralloc_t* out) +{ + PRINTF( + " This prints a table of snapshots that are stored in the snapshot storage\n" + " file that the emulator was started with, then exits. Values from the 'ID'\n" + " and 'TAG' columns can be used as arguments for the '-snapshot' parameter.\n\n" + + " If '-snapstorage <file>' was specified as well, this command prints a " + " table of the snapshots stored in <file>.\n\n" + + " See '-help-snapshot' for more information on snapshots.\n\n" + ); +} + #endif static void diff --git a/android/main.c b/android/main.c index a328ad1..6b3bea6 100644 --- a/android/main.c +++ b/android/main.c @@ -59,6 +59,8 @@ #include "android/qemulator.h" #include "android/display.h" +#include "android/snapshot.h" + #include "framebuffer.h" AndroidRotation android_framebuffer_rotation; @@ -1003,11 +1005,11 @@ int main(int argc, char **argv) if (!opts->snapstorage && opts->datadir) { bufprint(tmp, tmpend, "%s/snapshots.img", opts->datadir); if (path_exists(tmp)) { - opts->snapstorage = qemu_strdup(tmp); + opts->snapstorage = android_strdup(tmp); D("autoconfig: -snapstorage %s", opts->snapstorage); } } -#endif +#endif // CONFIG_ANDROID_SNAPSHOTS } /* setup the virtual device parameters from our options @@ -1423,7 +1425,11 @@ int main(int argc, char **argv) } else if (opts->no_snapshot) { D("ignoring redundant option '-no-snapshot' implied by '-no-snapstorage'"); } -#endif + + if (opts->snapshot_list) { + snapshot_print_and_exit(opts->snapstorage); + } +#endif // CONFIG_ANDROID_SNAPSHOTS if (!opts->logcat || opts->logcat[0] == 0) { opts->logcat = getenv("ANDROID_LOG_TAGS"); diff --git a/android/snapshot.c b/android/snapshot.c new file mode 100644 index 0000000..ca86700 --- /dev/null +++ b/android/snapshot.c @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Limited driver for the Qcow2 filesystem, capable of extracting snapshot + * metadata from Qcow2 formatted image file. + * + * Similar functionality is implemented in block/qcow2.c, block/qcow2-snapshot.c + * and block.c. This separate implementation was made to further decouple the UI + * of the Android emulator from the underlying Qemu system. It allows the UI to + * show snapshot listings without having to fall back on Qemu's block driver + * system, which would pull in a lot of code irrelevant for the UI. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "android/utils/debug.h" +#include "android/utils/system.h" +#include "bswap.h" +#include "snapshot.h" + +#if CONFIG_ANDROID_SNAPSHOTS + +/* "Magic" sequence of four bytes required by spec to be the first four bytes + * of any Qcow file. + */ +#define QCOW_MAGIC (('Q' << 24) | ('F' << 16) | ('I' << 8) | 0xfb) +#define QCOW_VERSION 2 + +/* Reads 'nbyte' bytes from 'fd' into 'buf', retrying on interrupts. + * Exit()s if the read fails for any other reason. + */ +static int +read_or_die(int fd, void *buf, size_t nbyte) +{ + int ret = 0; + do { + ret = read(fd, buf, nbyte); + } + while(ret < 0 && errno == EINTR); + + if (ret < 0) { + derror("read failed: %s", strerror(errno)); + exit(1); + } + + return ret; +} + +/* Wrapper around lseek(), exit()s on error. + */ +static off_t +seek_or_die(int fd, off_t offset, int whence) +{ + off_t ret = lseek(fd, offset, whence); + if (ret < 0) { + derror("seek failed: %s", strerror(errno)); + exit(1); + } + return ret; +} + + +typedef struct SnapshotInfo { + char *id_str; + char *name; + + uint32_t date_sec; + uint32_t date_nsec; + + uint64_t vm_clock_nsec; + uint32_t vm_state_size; +} SnapshotInfo; + +static SnapshotInfo* +snapshot_info_alloc() +{ + return android_alloc(sizeof(SnapshotInfo)); +} + +static void +snapshot_info_free( SnapshotInfo* info ) +{ + AFREE(info->id_str); + AFREE(info->name); + AFREE(info); +} + + +/* Reads a snapshot record from a qcow2-formatted file. + * + * The function assumes the file position of 'fd' points to the beginning of a + * QcowSnapshotHeader record. When the call returns, the file position of fd is + * at the place where the next QcowSnapshotHeader should start, if there is one. + * + * C.f. QCowSnapshotHeader in block/qcow2-snapshot.c for the complete layout of + * the header. + */ +static void +snapshot_info_read( int fd, SnapshotInfo* info ) +{ + uint64_t start_offset = seek_or_die(fd, 0, SEEK_CUR); + + uint32_t extra_data_size; + uint16_t id_str_size, name_size; + + /* read fixed-length fields */ + seek_or_die(fd, 12, SEEK_CUR); /* skip l1 info */ + read_or_die(fd, &id_str_size, sizeof(id_str_size)); + read_or_die(fd, &name_size, sizeof(name_size)); + read_or_die(fd, &info->date_sec, sizeof(info->date_sec)); + read_or_die(fd, &info->date_nsec, sizeof(info->date_nsec)); + read_or_die(fd, &info->vm_clock_nsec, sizeof(info->vm_clock_nsec)); + read_or_die(fd, &info->vm_state_size, sizeof(info->vm_state_size)); + read_or_die(fd, &extra_data_size, sizeof(extra_data_size)); + + /* convert to host endianness */ + be16_to_cpus(&id_str_size); + be16_to_cpus(&name_size); + be32_to_cpus(&info->date_sec); + be32_to_cpus(&info->date_nsec); + be64_to_cpus(&info->vm_clock_nsec); + be32_to_cpus(&info->vm_state_size); + be32_to_cpus(&extra_data_size); + be32_to_cpus(&extra_data_size); + + /* read variable-length buffers*/ + info->id_str = android_alloc(id_str_size + 1); // +1: manual null-termination + info->name = android_alloc(name_size + 1); + seek_or_die(fd, extra_data_size, SEEK_CUR); /* skip extra data */ + read_or_die(fd, info->id_str, id_str_size); + read_or_die(fd, info->name, name_size); + + info->id_str[id_str_size] = '\0'; + info->name[name_size] = '\0'; + + /* headers are 8 byte aligned, ceil to nearest multiple of 8 */ + uint64_t end_offset = seek_or_die(fd, 0, SEEK_CUR); + uint32_t total_size = end_offset - start_offset; + uint32_t aligned_size = ((total_size - 1) / 8 + 1) * 8; + + /* skip to start of next record */ + seek_or_die(fd, start_offset + aligned_size, SEEK_SET); +} + + +#define NB_SUFFIXES 4 + +/* Returns the size of a snapshot in a human-readable format. + * + * This function copyright (c) 2003 Fabrice Bellard + */ +static char* +snapshot_format_size( char *buf, int buf_size, int64_t size ) +{ + static const char suffixes[NB_SUFFIXES] = "KMGT"; + int64_t base; + int i; + + if (size <= 999) { + snprintf(buf, buf_size, "%" PRId64, size); + } else { + base = 1024; + for(i = 0; i < NB_SUFFIXES; i++) { + if (size < (10 * base)) { + snprintf(buf, buf_size, "%0.1f%c", + (double)size / base, + suffixes[i]); + break; + } else if (size < (1000 * base) || i == (NB_SUFFIXES - 1)) { + snprintf(buf, buf_size, "%" PRId64 "%c", + ((size + (base >> 1)) / base), + suffixes[i]); + break; + } + base = base * 1024; + } + } + return buf; +} + +static char* +snapshot_format_create_date( char *buf, size_t buf_size, time_t *time ) +{ + struct tm *tm; + tm = localtime(time); + strftime(buf, buf_size, "%Y-%m-%d %H:%M:%S", tm); + return buf; +} + +static char* +snapshot_format_vm_clock( char *buf, size_t buf_size, uint64_t vm_clock_nsec ) +{ + uint64_t secs = vm_clock_nsec / 1000000000; + snprintf(buf, buf_size, "%02d:%02d:%02d.%03d", + (int)(secs / 3600), + (int)((secs / 60) % 60), + (int)(secs % 60), + (int)((vm_clock_nsec / 1000000) % 1000)); + return buf; +} + +/* Prints a row of the snapshot table to stdout. */ +static void +snapshot_info_print( SnapshotInfo *info ) +{ + char size_buf[8]; + char date_buf[21]; + char clock_buf[21]; + + snapshot_format_size(size_buf, sizeof(size_buf), info->vm_state_size); + snapshot_format_create_date(date_buf, sizeof(date_buf), + (time_t*) &info->date_sec); + snapshot_format_vm_clock(clock_buf, sizeof(clock_buf), info->vm_clock_nsec); + + printf(" %-10s%-20s%7s%20s%15s\n", + info->id_str, info->name, size_buf, date_buf, clock_buf); +} + +/* Prints table of all snapshots recorded in the file 'fd'. + */ +static void +snapshot_print_table( int fd, uint32_t nb_snapshots, uint64_t snapshots_offset ) +{ + printf(" %-10s%-20s%7s%20s%15s\n", + "ID", "TAG", "VM SIZE", "DATE", "VM CLOCK"); + + /* skip ahead to snapshot data */ + seek_or_die(fd, snapshots_offset, SEEK_SET); + + /* iterate over snapshot records */ + int i; + for (i = 0; i < nb_snapshots; i++) { + SnapshotInfo *info = snapshot_info_alloc(); + snapshot_info_read(fd, info); + snapshot_info_print(info); + + snapshot_info_free(info); + } +} + +/* Validates that 'fd' starts with a correct Qcow 2 header. Prints an error and + * exit()s if validation fails. + */ +static void +snapshot_validate_qcow_file( int fd ) +{ + /* read magic number and qcow version (2x4 bytes) */ + uint32_t magic, version; + read_or_die(fd, &magic, sizeof(magic)); + read_or_die(fd, &version, sizeof(version)); + be32_to_cpus(&magic); + be32_to_cpus(&version); + + if (magic != QCOW_MAGIC) { + derror("Not a valid Qcow snapshot file (expected magic value '%08x', got '%08x').", + QCOW_MAGIC, magic); + exit(1); + } + if (version != QCOW_VERSION) { + derror("Unsupported Qcow version (need %d, got %d).", + QCOW_VERSION, version); + exit(1); + } +} + +/* Reads snapshot information from a Qcow2 file header. + * + * C.f. QCowHeader in block/qcow2.h for an exact listing of the header + * contents. + */ +static void +snapshot_read_qcow_header( int fd, uint32_t *nb_snapshots, uint64_t *snapshots_offset ) +{ + snapshot_validate_qcow_file(fd); + + /* skip non-snapshot related metadata (4x8 + 5x4 = 52 bytes)*/ + seek_or_die(fd, 52, SEEK_CUR); + + read_or_die(fd, nb_snapshots, sizeof(*nb_snapshots)); + read_or_die(fd, snapshots_offset, sizeof(*snapshots_offset)); + + /* convert to host endianness */ + be32_to_cpus(nb_snapshots); + be64_to_cpus(snapshots_offset); +} + +/* Prints a table with information on the snapshots in the qcow2-formatted file + * 'snapstorage', then exit()s. + */ +void +snapshot_print_and_exit( const char *snapstorage ) +{ + /* open snapshot file */ + int fd = open(snapstorage, O_RDONLY); + if (!fd) { + derror("Could not open snapshot file '%s': %s", snapstorage, strerror(errno)); + exit(1); + } + + /* read snapshot info from file header */ + uint32_t nb_snapshots; + uint64_t snapshots_offset; + snapshot_read_qcow_header(fd, &nb_snapshots, &snapshots_offset); + + if (nb_snapshots > 0) { + printf("Snapshots in file '%s':\n", snapstorage); + snapshot_print_table(fd, nb_snapshots, snapshots_offset); + } + else { + printf("File '%s' contains no snapshots yet.\n", snapstorage); + } + + close(fd); + exit(0); +} +#endif // CONFIG_ANDROID_SNAPSHOTS diff --git a/android/snapshot.h b/android/snapshot.h new file mode 100644 index 0000000..be03818 --- /dev/null +++ b/android/snapshot.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SNAPSHOTS_H_ +#define SNAPSHOTS_H_ + +#include "config/config.h" + +#if CONFIG_ANDROID_SNAPSHOTS + +/* Prints a table with information on the snapshot stored in the file + * 'snapstorage', then exit()s. + */ +void snapshot_print_and_exit( const char *snapstorage ); + +#endif + +#endif /* SNAPSHOTS_H_ */ |