1 /* commands/sysloader/installer/installer.c 2 * 3 * Copyright 2008, The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 #define LOG_TAG "installer" 19 20 #include <sys/types.h> 21 #include <errno.h> 22 #include <fcntl.h> 23 #include <stdint.h> 24 #include <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <unistd.h> 28 #include <sys/mount.h> 29 #include <sys/stat.h> 30 #include <sys/wait.h> 31 32 33 #include <cutils/config_utils.h> 34 #include <cutils/log.h> 35 36 #include "diskconfig/diskconfig.h" 37 #include "installer.h" 38 39 #define MKE2FS_BIN "/system/bin/mke2fs" 40 #define E2FSCK_BIN "/system/bin/e2fsck" 41 #define TUNE2FS_BIN "/system/bin/tune2fs" 42 #define RESIZE2FS_BIN "/system/bin/resize2fs" 43 44 static int 45 usage(void) 46 { 47 fprintf(stderr, "Usage: %s\n", LOG_TAG); 48 fprintf(stderr, "\t-c <path> - Path to installer conf file " 49 "(/system/etc/installer.conf)\n"); 50 fprintf(stderr, "\t-l <path> - Path to device disk layout conf file " 51 "(/system/etc/disk_layout.conf)\n"); 52 fprintf(stderr, "\t-h - This help message\n"); 53 fprintf(stderr, "\t-d - Dump the compiled in partition info.\n"); 54 fprintf(stderr, "\t-p <path> - Path to device that should be mounted" 55 " to /data.\n"); 56 fprintf(stderr, "\t-t - Test mode. Don't write anything to disk.\n"); 57 return 1; 58 } 59 60 static cnode * 61 read_conf_file(const char *fn) 62 { 63 cnode *root = config_node("", ""); 64 config_load_file(root, fn); 65 66 if (root->first_child == NULL) { 67 LOGE("Could not read config file %s", fn); 68 return NULL; 69 } 70 71 return root; 72 } 73 74 static int 75 exec_cmd(const char *cmd, ...) /* const char *arg, ...) */ 76 { 77 va_list ap; 78 int size = 0; 79 char *str; 80 char *outbuf; 81 int rv; 82 83 /* compute the size for the command buffer */ 84 size = strlen(cmd) + 1; 85 va_start(ap, cmd); 86 while ((str = va_arg(ap, char *))) { 87 size += strlen(str) + 1; /* need room for the space separator */ 88 } 89 va_end(ap); 90 91 if (!(outbuf = malloc(size + 1))) { 92 LOGE("Can't allocate memory to exec cmd"); 93 return -1; 94 } 95 96 /* this is a bit inefficient, but is trivial, and works */ 97 strcpy(outbuf, cmd); 98 va_start(ap, cmd); 99 while ((str = va_arg(ap, char *))) { 100 strcat(outbuf, " "); 101 strcat(outbuf, str); 102 } 103 va_end(ap); 104 105 LOGI("Executing: %s", outbuf); 106 rv = system(outbuf); 107 free(outbuf); 108 if (rv < 0) { 109 LOGI("Error while trying to execute '%s'", cmd); 110 return -1; 111 } 112 rv = WEXITSTATUS(rv); 113 LOGI("Done executing %s (%d)", outbuf, rv); 114 return rv; 115 } 116 117 118 static int 119 do_fsck(const char *dst, int force) 120 { 121 int rv; 122 const char *opts = force ? "-fy" : "-y"; 123 124 125 LOGI("Running e2fsck... (force=%d) This MAY take a while.", force); 126 if ((rv = exec_cmd(E2FSCK_BIN, "-C 0", opts, dst, NULL)) < 0) 127 return 1; 128 if (rv >= 4) { 129 LOGE("Error while running e2fsck: %d", rv); 130 return 1; 131 } 132 sync(); 133 LOGI("e2fsck succeeded (exit code: %d)", rv); 134 135 return 0; 136 } 137 138 static int 139 process_ext2_image(const char *dst, const char *src, uint32_t flags, int test) 140 { 141 int rv; 142 143 /* First, write the image to disk. */ 144 if (write_raw_image(dst, src, 0, test)) 145 return 1; 146 147 if (test) 148 return 0; 149 150 /* Next, let's e2fsck the fs to make sure it got written ok, and 151 * everything is peachy */ 152 if (do_fsck(dst, 1)) 153 return 1; 154 155 /* set the mount count to 1 so that 1st mount on boot doesn't complain */ 156 if ((rv = exec_cmd(TUNE2FS_BIN, "-C", "1", dst, NULL)) < 0) 157 return 1; 158 if (rv) { 159 LOGE("Error while running tune2fs: %d", rv); 160 return 1; 161 } 162 163 /* If the user requested that we resize, let's do it now */ 164 if (flags & INSTALL_FLAG_RESIZE) { 165 if ((rv = exec_cmd(RESIZE2FS_BIN, "-F", dst, NULL)) < 0) 166 return 1; 167 if (rv) { 168 LOGE("Error while running resize2fs: %d", rv); 169 return 1; 170 } 171 sync(); 172 if (do_fsck(dst, 0)) 173 return 1; 174 } 175 176 /* make this an ext3 fs? */ 177 if (flags & INSTALL_FLAG_ADDJOURNAL) { 178 if ((rv = exec_cmd(TUNE2FS_BIN, "-j", dst, NULL)) < 0) 179 return 1; 180 if (rv) { 181 LOGE("Error while running tune2fs: %d", rv); 182 return 1; 183 } 184 sync(); 185 if (do_fsck(dst, 0)) 186 return 1; 187 } 188 189 return 0; 190 } 191 192 193 /* TODO: PLEASE break up this function into several functions that just 194 * do what they need with the image node. Many of them will end up 195 * looking at same strings, but it will be sooo much cleaner */ 196 static int 197 process_image_node(cnode *img, struct disk_info *dinfo, int test) 198 { 199 struct part_info *pinfo = NULL; 200 loff_t offset = (loff_t)-1; 201 const char *filename = NULL; 202 char *dest_part = NULL; 203 const char *tmp; 204 uint32_t flags = 0; 205 uint8_t type = 0; 206 int rv; 207 int func_ret = 1; 208 209 filename = config_str(img, "filename", NULL); 210 211 /* process the 'offset' image parameter */ 212 if ((tmp = config_str(img, "offset", NULL)) != NULL) 213 offset = strtoull(tmp, NULL, 0); 214 215 /* process the 'partition' image parameter */ 216 if ((tmp = config_str(img, "partition", NULL)) != NULL) { 217 if (offset != (loff_t)-1) { 218 LOGE("Cannot specify the partition name AND an offset for %s", 219 img->name); 220 goto fail; 221 } 222 223 if (!(pinfo = find_part(dinfo, tmp))) { 224 LOGE("Cannot find partition %s while processing %s", 225 tmp, img->name); 226 goto fail; 227 } 228 229 if (!(dest_part = find_part_device(dinfo, pinfo->name))) { 230 LOGE("Could not get the device name for partition %s while" 231 " processing image %s", pinfo->name, img->name); 232 goto fail; 233 } 234 offset = pinfo->start_lba * dinfo->sect_size; 235 } 236 237 /* process the 'mkfs' parameter */ 238 if ((tmp = config_str(img, "mkfs", NULL)) != NULL) { 239 char *journal_opts; 240 char vol_lbl[16]; /* ext2/3 has a 16-char volume label */ 241 242 if (!pinfo) { 243 LOGE("Target partition required for mkfs for '%s'", img->name); 244 goto fail; 245 } else if (filename) { 246 LOGE("Providing filename and mkfs parameters is meaningless"); 247 goto fail; 248 } 249 250 if (!strcmp(tmp, "ext4")) 251 journal_opts = ""; 252 else if (!strcmp(tmp, "ext2")) 253 journal_opts = ""; 254 else if (!strcmp(tmp, "ext3")) 255 journal_opts = "-j"; 256 else { 257 LOGE("Unknown filesystem type for mkfs: %s", tmp); 258 goto fail; 259 } 260 261 /* put the partition name as the volume label */ 262 strncpy(vol_lbl, pinfo->name, sizeof(vol_lbl)); 263 264 /* since everything checked out, lets make the fs, and return since 265 * we don't need to do anything else */ 266 rv = exec_cmd(MKE2FS_BIN, "-L", vol_lbl, journal_opts, dest_part, NULL); 267 if (rv < 0) 268 goto fail; 269 else if (rv > 0) { 270 LOGE("Error while running mke2fs: %d", rv); 271 goto fail; 272 } 273 sync(); 274 if (do_fsck(dest_part, 0)) 275 goto fail; 276 goto done; 277 } 278 279 /* since we didn't mkfs above, all the rest of the options assume 280 * there's a filename involved */ 281 if (!filename) { 282 LOGE("Filename is required for image %s", img->name); 283 goto fail; 284 } 285 286 /* process the 'flags' image parameter */ 287 if ((tmp = config_str(img, "flags", NULL)) != NULL) { 288 char *flagstr, *flagstr_orig; 289 290 if (!(flagstr = flagstr_orig = strdup(tmp))) { 291 LOGE("Cannot allocate memory for dup'd flags string"); 292 goto fail; 293 } 294 while ((tmp = strsep(&flagstr, ","))) { 295 if (!strcmp(tmp, "resize")) 296 flags |= INSTALL_FLAG_RESIZE; 297 else if (!strcmp(tmp, "addjournal")) 298 flags |= INSTALL_FLAG_ADDJOURNAL; 299 else { 300 LOGE("Unknown flag '%s' for image %s", tmp, img->name); 301 free(flagstr_orig); 302 goto fail; 303 } 304 } 305 free(flagstr_orig); 306 } 307 308 /* process the 'type' image parameter */ 309 if (!(tmp = config_str(img, "type", NULL))) { 310 LOGE("Type is required for image %s", img->name); 311 goto fail; 312 } else if (!strcmp(tmp, "raw")) { 313 type = INSTALL_IMAGE_RAW; 314 } else if (!strcmp(tmp, "ext2")) { 315 type = INSTALL_IMAGE_EXT2; 316 } else if (!strcmp(tmp, "ext3")) { 317 type = INSTALL_IMAGE_EXT3; 318 } else if (!strcmp(tmp, "ext4")) { 319 type = INSTALL_IMAGE_EXT4; 320 } else { 321 LOGE("Unknown image type '%s' for image %s", tmp, img->name); 322 goto fail; 323 } 324 325 /* at this point we MUST either have a partition in 'pinfo' or a raw 326 * 'offset', otherwise quit */ 327 if (!pinfo && (offset == (loff_t)-1)) { 328 LOGE("Offset to write into the disk is unknown for %s", img->name); 329 goto fail; 330 } 331 332 if (!pinfo && (type != INSTALL_IMAGE_RAW)) { 333 LOGE("Only raw images can specify direct offset on the disk. Please" 334 " specify the target partition name instead. (%s)", img->name); 335 goto fail; 336 } 337 338 switch(type) { 339 case INSTALL_IMAGE_RAW: 340 if (write_raw_image(dinfo->device, filename, offset, test)) 341 goto fail; 342 break; 343 344 case INSTALL_IMAGE_EXT3: 345 /* makes the error checking in the imager function easier */ 346 if (flags & INSTALL_FLAG_ADDJOURNAL) { 347 LOGW("addjournal flag is meaningless for ext3 images"); 348 flags &= ~INSTALL_FLAG_ADDJOURNAL; 349 } 350 /* ...fall through... */ 351 352 case INSTALL_IMAGE_EXT4: 353 /* fallthru */ 354 355 case INSTALL_IMAGE_EXT2: 356 if (process_ext2_image(dest_part, filename, flags, test)) 357 goto fail; 358 break; 359 360 default: 361 LOGE("Unknown image type: %d", type); 362 goto fail; 363 } 364 365 done: 366 func_ret = 0; 367 368 fail: 369 if (dest_part) 370 free(dest_part); 371 return func_ret; 372 } 373 374 int 375 main(int argc, char *argv[]) 376 { 377 char *disk_conf_file = "/system/etc/disk_layout.conf"; 378 char *inst_conf_file = "/system/etc/installer.conf"; 379 char *inst_data_dir = "/data"; 380 char *inst_data_dev = NULL; 381 char *data_fstype = "ext4"; 382 cnode *config; 383 cnode *images; 384 cnode *img; 385 int cnt = 0; 386 struct disk_info *device_disk_info; 387 int dump = 0; 388 int test = 0; 389 int x; 390 391 while ((x = getopt (argc, argv, "thdc:l:p:")) != EOF) { 392 switch (x) { 393 case 'h': 394 return usage(); 395 case 'c': 396 inst_conf_file = optarg; 397 break; 398 case 'l': 399 disk_conf_file = optarg; 400 break; 401 case 't': 402 test = 1; 403 break; 404 case 'p': 405 inst_data_dev = optarg; 406 break; 407 case 'd': 408 dump = 1; 409 break; 410 default: 411 fprintf(stderr, "Unknown argument: %c\n", (char)optopt); 412 return usage(); 413 } 414 } 415 416 /* If the user asked us to wait for data device, wait for it to appear, 417 * and then mount it onto /data */ 418 if (inst_data_dev && !dump) { 419 struct stat filestat; 420 421 LOGI("Waiting for device: %s", inst_data_dev); 422 while (stat(inst_data_dev, &filestat)) 423 sleep(1); 424 LOGI("Device %s ready", inst_data_dev); 425 if (mount(inst_data_dev, inst_data_dir, data_fstype, MS_RDONLY, NULL)) { 426 LOGE("Could not mount %s on %s as %s", inst_data_dev, inst_data_dir, 427 data_fstype); 428 return 1; 429 } 430 } 431 432 /* Read and process the disk configuration */ 433 if (!(device_disk_info = load_diskconfig(disk_conf_file, NULL))) { 434 LOGE("Errors encountered while loading disk conf file %s", 435 disk_conf_file); 436 return 1; 437 } 438 439 if (process_disk_config(device_disk_info)) { 440 LOGE("Errors encountered while processing disk config from %s", 441 disk_conf_file); 442 return 1; 443 } 444 445 /* Was all of this for educational purposes? If so, quit. */ 446 if (dump) { 447 dump_disk_config(device_disk_info); 448 return 0; 449 } 450 451 /* This doesnt do anything but load the config file */ 452 if (!(config = read_conf_file(inst_conf_file))) 453 return 1; 454 455 /* First, partition the drive */ 456 if (apply_disk_config(device_disk_info, test)) 457 return 1; 458 459 /* Now process the installer config file and write the images to disk */ 460 if (!(images = config_find(config, "images"))) { 461 LOGE("Invalid configuration file %s. Missing 'images' section", 462 inst_conf_file); 463 return 1; 464 } 465 466 for (img = images->first_child; img; img = img->next) { 467 if (process_image_node(img, device_disk_info, test)) { 468 LOGE("Unable to write data to partition. Try running 'installer' again."); 469 return 1; 470 } 471 ++cnt; 472 } 473 474 /* 475 * We have to do the apply() twice. We must do it once before the image 476 * writes to layout the disk partitions so that we can write images to 477 * them. We then do the apply() again in case one of the images 478 * replaced the MBR with a new bootloader, and thus messed with 479 * partition table. 480 */ 481 if (apply_disk_config(device_disk_info, test)) 482 return 1; 483 484 LOGI("Done processing installer config. Configured %d images", cnt); 485 LOGI("Type 'reboot' or reset to run new image"); 486 return 0; 487 } 488