/* commands/sysloader/installer/installer.c * * Copyright 2008, 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. */ #define LOG_TAG "installer" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "diskconfig/diskconfig.h" #include "installer.h" #define MKE2FS_BIN "/system/bin/mke2fs" #define E2FSCK_BIN "/system/bin/e2fsck" #define TUNE2FS_BIN "/system/bin/tune2fs" #define RESIZE2FS_BIN "/system/bin/resize2fs" static int usage(void) { fprintf(stderr, "Usage: %s\n", LOG_TAG); fprintf(stderr, "\t-c - Path to installer conf file " "(/system/etc/installer.conf)\n"); fprintf(stderr, "\t-l - Path to device disk layout conf file " "(/system/etc/disk_layout.conf)\n"); fprintf(stderr, "\t-h - This help message\n"); fprintf(stderr, "\t-d - Dump the compiled in partition info.\n"); fprintf(stderr, "\t-p - Path to device that should be mounted" " to /data.\n"); fprintf(stderr, "\t-t - Test mode. Don't write anything to disk.\n"); return 1; } static cnode * read_conf_file(const char *fn) { cnode *root = config_node("", ""); config_load_file(root, fn); if (root->first_child == NULL) { ALOGE("Could not read config file %s", fn); return NULL; } return root; } static int exec_cmd(const char *cmd, ...) /* const char *arg, ...) */ { va_list ap; int size = 0; char *str; char *outbuf; int rv; /* compute the size for the command buffer */ size = strlen(cmd) + 1; va_start(ap, cmd); while ((str = va_arg(ap, char *))) { size += strlen(str) + 1; /* need room for the space separator */ } va_end(ap); if (!(outbuf = malloc(size + 1))) { ALOGE("Can't allocate memory to exec cmd"); return -1; } /* this is a bit inefficient, but is trivial, and works */ strcpy(outbuf, cmd); va_start(ap, cmd); while ((str = va_arg(ap, char *))) { strcat(outbuf, " "); strcat(outbuf, str); } va_end(ap); ALOGI("Executing: %s", outbuf); rv = system(outbuf); free(outbuf); if (rv < 0) { ALOGI("Error while trying to execute '%s'", cmd); return -1; } rv = WEXITSTATUS(rv); ALOGI("Done executing %s (%d)", outbuf, rv); return rv; } static int do_fsck(const char *dst, int force) { int rv; const char *opts = force ? "-fy" : "-y"; ALOGI("Running e2fsck... (force=%d) This MAY take a while.", force); if ((rv = exec_cmd(E2FSCK_BIN, "-C 0", opts, dst, NULL)) < 0) return 1; if (rv >= 4) { ALOGE("Error while running e2fsck: %d", rv); return 1; } sync(); ALOGI("e2fsck succeeded (exit code: %d)", rv); return 0; } static int process_ext2_image(const char *dst, const char *src, uint32_t flags, int test) { int rv; /* First, write the image to disk. */ if (write_raw_image(dst, src, 0, test)) return 1; if (test) return 0; /* Next, let's e2fsck the fs to make sure it got written ok, and * everything is peachy */ if (do_fsck(dst, 1)) return 1; /* set the mount count to 1 so that 1st mount on boot doesn't complain */ if ((rv = exec_cmd(TUNE2FS_BIN, "-C", "1", dst, NULL)) < 0) return 1; if (rv) { ALOGE("Error while running tune2fs: %d", rv); return 1; } /* If the user requested that we resize, let's do it now */ if (flags & INSTALL_FLAG_RESIZE) { if ((rv = exec_cmd(RESIZE2FS_BIN, "-F", dst, NULL)) < 0) return 1; if (rv) { ALOGE("Error while running resize2fs: %d", rv); return 1; } sync(); if (do_fsck(dst, 0)) return 1; } /* make this an ext3 fs? */ if (flags & INSTALL_FLAG_ADDJOURNAL) { if ((rv = exec_cmd(TUNE2FS_BIN, "-j", dst, NULL)) < 0) return 1; if (rv) { ALOGE("Error while running tune2fs: %d", rv); return 1; } sync(); if (do_fsck(dst, 0)) return 1; } return 0; } /* TODO: PLEASE break up this function into several functions that just * do what they need with the image node. Many of them will end up * looking at same strings, but it will be sooo much cleaner */ static int process_image_node(cnode *img, struct disk_info *dinfo, int test) { struct part_info *pinfo = NULL; loff_t offset = (loff_t)-1; const char *filename = NULL; char *dest_part = NULL; const char *tmp; uint32_t flags = 0; uint8_t type = 0; int rv; int func_ret = 1; filename = config_str(img, "filename", NULL); /* process the 'offset' image parameter */ if ((tmp = config_str(img, "offset", NULL)) != NULL) offset = strtoull(tmp, NULL, 0); /* process the 'partition' image parameter */ if ((tmp = config_str(img, "partition", NULL)) != NULL) { if (offset != (loff_t)-1) { ALOGE("Cannot specify the partition name AND an offset for %s", img->name); goto fail; } if (!(pinfo = find_part(dinfo, tmp))) { ALOGE("Cannot find partition %s while processing %s", tmp, img->name); goto fail; } if (!(dest_part = find_part_device(dinfo, pinfo->name))) { ALOGE("Could not get the device name for partition %s while" " processing image %s", pinfo->name, img->name); goto fail; } offset = pinfo->start_lba * dinfo->sect_size; } /* process the 'mkfs' parameter */ if ((tmp = config_str(img, "mkfs", NULL)) != NULL) { char *journal_opts; char vol_lbl[16]; /* ext2/3 has a 16-char volume label */ if (!pinfo) { ALOGE("Target partition required for mkfs for '%s'", img->name); goto fail; } else if (filename) { ALOGE("Providing filename and mkfs parameters is meaningless"); goto fail; } if (!strcmp(tmp, "ext4")) journal_opts = ""; else if (!strcmp(tmp, "ext2")) journal_opts = ""; else if (!strcmp(tmp, "ext3")) journal_opts = "-j"; else { ALOGE("Unknown filesystem type for mkfs: %s", tmp); goto fail; } /* put the partition name as the volume label */ strncpy(vol_lbl, pinfo->name, sizeof(vol_lbl)); /* since everything checked out, lets make the fs, and return since * we don't need to do anything else */ rv = exec_cmd(MKE2FS_BIN, "-L", vol_lbl, journal_opts, dest_part, NULL); if (rv < 0) goto fail; else if (rv > 0) { ALOGE("Error while running mke2fs: %d", rv); goto fail; } sync(); if (do_fsck(dest_part, 0)) goto fail; goto done; } /* since we didn't mkfs above, all the rest of the options assume * there's a filename involved */ if (!filename) { ALOGE("Filename is required for image %s", img->name); goto fail; } /* process the 'flags' image parameter */ if ((tmp = config_str(img, "flags", NULL)) != NULL) { char *flagstr, *flagstr_orig; if (!(flagstr = flagstr_orig = strdup(tmp))) { ALOGE("Cannot allocate memory for dup'd flags string"); goto fail; } while ((tmp = strsep(&flagstr, ","))) { if (!strcmp(tmp, "resize")) flags |= INSTALL_FLAG_RESIZE; else if (!strcmp(tmp, "addjournal")) flags |= INSTALL_FLAG_ADDJOURNAL; else { ALOGE("Unknown flag '%s' for image %s", tmp, img->name); free(flagstr_orig); goto fail; } } free(flagstr_orig); } /* process the 'type' image parameter */ if (!(tmp = config_str(img, "type", NULL))) { ALOGE("Type is required for image %s", img->name); goto fail; } else if (!strcmp(tmp, "raw")) { type = INSTALL_IMAGE_RAW; } else if (!strcmp(tmp, "ext2")) { type = INSTALL_IMAGE_EXT2; } else if (!strcmp(tmp, "ext3")) { type = INSTALL_IMAGE_EXT3; } else if (!strcmp(tmp, "ext4")) { type = INSTALL_IMAGE_EXT4; } else { ALOGE("Unknown image type '%s' for image %s", tmp, img->name); goto fail; } /* at this point we MUST either have a partition in 'pinfo' or a raw * 'offset', otherwise quit */ if (!pinfo && (offset == (loff_t)-1)) { ALOGE("Offset to write into the disk is unknown for %s", img->name); goto fail; } if (!pinfo && (type != INSTALL_IMAGE_RAW)) { ALOGE("Only raw images can specify direct offset on the disk. Please" " specify the target partition name instead. (%s)", img->name); goto fail; } switch(type) { case INSTALL_IMAGE_RAW: if (write_raw_image(dinfo->device, filename, offset, test)) goto fail; break; case INSTALL_IMAGE_EXT3: /* makes the error checking in the imager function easier */ if (flags & INSTALL_FLAG_ADDJOURNAL) { ALOGW("addjournal flag is meaningless for ext3 images"); flags &= ~INSTALL_FLAG_ADDJOURNAL; } /* ...fall through... */ case INSTALL_IMAGE_EXT4: /* fallthru */ case INSTALL_IMAGE_EXT2: if (process_ext2_image(dest_part, filename, flags, test)) goto fail; break; default: ALOGE("Unknown image type: %d", type); goto fail; } done: func_ret = 0; fail: if (dest_part) free(dest_part); return func_ret; } int main(int argc, char *argv[]) { char *disk_conf_file = "/system/etc/disk_layout.conf"; char *inst_conf_file = "/system/etc/installer.conf"; char *inst_data_dir = "/data"; char *inst_data_dev = NULL; char *data_fstype = "ext4"; cnode *config; cnode *images; cnode *img; int cnt = 0; struct disk_info *device_disk_info; int dump = 0; int test = 0; int x; while ((x = getopt (argc, argv, "thdc:l:p:")) != EOF) { switch (x) { case 'h': return usage(); case 'c': inst_conf_file = optarg; break; case 'l': disk_conf_file = optarg; break; case 't': test = 1; break; case 'p': inst_data_dev = optarg; break; case 'd': dump = 1; break; default: fprintf(stderr, "Unknown argument: %c\n", (char)optopt); return usage(); } } /* If the user asked us to wait for data device, wait for it to appear, * and then mount it onto /data */ if (inst_data_dev && !dump) { struct stat filestat; ALOGI("Waiting for device: %s", inst_data_dev); while (stat(inst_data_dev, &filestat)) sleep(1); ALOGI("Device %s ready", inst_data_dev); if (mount(inst_data_dev, inst_data_dir, data_fstype, MS_RDONLY, NULL)) { ALOGE("Could not mount %s on %s as %s", inst_data_dev, inst_data_dir, data_fstype); return 1; } } /* Read and process the disk configuration */ if (!(device_disk_info = load_diskconfig(disk_conf_file, NULL))) { ALOGE("Errors encountered while loading disk conf file %s", disk_conf_file); return 1; } if (process_disk_config(device_disk_info)) { ALOGE("Errors encountered while processing disk config from %s", disk_conf_file); return 1; } /* Was all of this for educational purposes? If so, quit. */ if (dump) { dump_disk_config(device_disk_info); return 0; } /* This doesnt do anything but load the config file */ if (!(config = read_conf_file(inst_conf_file))) return 1; /* First, partition the drive */ if (apply_disk_config(device_disk_info, test)) return 1; /* Now process the installer config file and write the images to disk */ if (!(images = config_find(config, "images"))) { ALOGE("Invalid configuration file %s. Missing 'images' section", inst_conf_file); return 1; } for (img = images->first_child; img; img = img->next) { if (process_image_node(img, device_disk_info, test)) { ALOGE("Unable to write data to partition. Try running 'installer' again."); return 1; } ++cnt; } /* * We have to do the apply() twice. We must do it once before the image * writes to layout the disk partitions so that we can write images to * them. We then do the apply() again in case one of the images * replaced the MBR with a new bootloader, and thus messed with * partition table. */ if (apply_disk_config(device_disk_info, test)) return 1; ALOGI("Done processing installer config. Configured %d images", cnt); ALOGI("Type 'reboot' or reset to run new image"); return 0; }