Home | History | Annotate | Download | only in rawbu
      1 // Copyright 2009 The Android Open Source Project
      2 
      3 #include <stdio.h>
      4 #include <stdlib.h>
      5 #include <stdarg.h>
      6 #include <string.h>
      7 #include <unistd.h>
      8 #include <fcntl.h>
      9 #include <time.h>
     10 #include <dirent.h>
     11 #include <errno.h>
     12 #include <assert.h>
     13 #include <ctype.h>
     14 #include <utime.h>
     15 #include <sys/stat.h>
     16 #include <sys/types.h>
     17 #include <stdint.h>
     18 
     19 #include <cutils/properties.h>
     20 
     21 #include <private/android_filesystem_config.h>
     22 
     23 #ifndef PATH_MAX
     24 #define PATH_MAX 4096
     25 #endif
     26 
     27 // First version.
     28 #define FILE_VERSION_1 0xffff0001
     29 
     30 // Introduces backup all option to header.
     31 #define FILE_VERSION_2 0xffff0002
     32 
     33 #define FILE_VERSION FILE_VERSION_2
     34 
     35 namespace android {
     36 
     37 static char nameBuffer[PATH_MAX];
     38 static struct stat statBuffer;
     39 
     40 static char copyBuffer[8192];
     41 static char *backupFilePath = NULL;
     42 
     43 static uint32_t inputFileVersion;
     44 
     45 static int opt_backupAll;
     46 
     47 #define SPECIAL_NO_TOUCH 0
     48 #define SPECIAL_NO_BACKUP 1
     49 
     50 struct special_dir {
     51     const char* path;
     52     int type;
     53 };
     54 
     55 /* Directory paths that we will not backup/restore */
     56 static const struct special_dir SKIP_PATHS[] = {
     57     { "/data/misc", SPECIAL_NO_TOUCH },
     58     { "/data/system/batterystats.bin", SPECIAL_NO_TOUCH },
     59     { "/data/system/location", SPECIAL_NO_TOUCH },
     60     { "/data/dalvik-cache", SPECIAL_NO_BACKUP },
     61     { NULL, 0 },
     62 };
     63 
     64 /* This is just copied from the shell's built-in wipe command. */
     65 static int wipe (const char *path)
     66 {
     67     DIR *dir;
     68     struct dirent *de;
     69     int ret;
     70     int i;
     71 
     72     dir = opendir(path);
     73 
     74     if (dir == NULL) {
     75         fprintf (stderr, "Error opendir'ing %s: %s\n",
     76                     path, strerror(errno));
     77         return 0;
     78     }
     79 
     80     char *filenameOffset;
     81 
     82     strcpy(nameBuffer, path);
     83     strcat(nameBuffer, "/");
     84 
     85     filenameOffset = nameBuffer + strlen(nameBuffer);
     86 
     87     for (;;) {
     88         de = readdir(dir);
     89 
     90         if (de == NULL) {
     91             break;
     92         }
     93 
     94         if (0 == strcmp(de->d_name, ".")
     95                 || 0 == strcmp(de->d_name, "..")
     96                 || 0 == strcmp(de->d_name, "lost+found")
     97         ) {
     98             continue;
     99         }
    100 
    101         strcpy(filenameOffset, de->d_name);
    102         bool noBackup = false;
    103 
    104         /* See if this is a path we should skip. */
    105         for (i = 0; SKIP_PATHS[i].path; i++) {
    106             if (strcmp(SKIP_PATHS[i].path, nameBuffer) == 0) {
    107                 if (opt_backupAll || SKIP_PATHS[i].type == SPECIAL_NO_BACKUP) {
    108                     // In this case we didn't back up the directory --
    109                     // we do want to wipe its contents, but not the
    110                     // directory itself, since the restore file won't
    111                     // contain the directory.
    112                     noBackup = true;
    113                 }
    114                 break;
    115             }
    116         }
    117 
    118         if (!noBackup && SKIP_PATHS[i].path != NULL) {
    119             // This is a SPECIAL_NO_TOUCH directory.
    120             continue;
    121         }
    122 
    123         ret = lstat (nameBuffer, &statBuffer);
    124 
    125         if (ret != 0) {
    126             fprintf(stderr, "warning -- stat() error on '%s': %s\n",
    127                     nameBuffer, strerror(errno));
    128             continue;
    129         }
    130 
    131         if(S_ISDIR(statBuffer.st_mode)) {
    132             int i;
    133             char *newpath;
    134 
    135             newpath = strdup(nameBuffer);
    136             if (wipe(newpath) == 0) {
    137                 free(newpath);
    138                 closedir(dir);
    139                 return 0;
    140             }
    141 
    142             if (!noBackup) {
    143                 ret = rmdir(newpath);
    144                 if (ret != 0) {
    145                     fprintf(stderr, "warning -- rmdir() error on '%s': %s\n",
    146                         newpath, strerror(errno));
    147                 }
    148             }
    149 
    150             free(newpath);
    151 
    152             strcpy(nameBuffer, path);
    153             strcat(nameBuffer, "/");
    154 
    155         } else {
    156             // Don't delete the backup file
    157             if (backupFilePath && strcmp(backupFilePath, nameBuffer) == 0) {
    158                 continue;
    159             }
    160             ret = unlink(nameBuffer);
    161 
    162             if (ret != 0) {
    163                 fprintf(stderr, "warning -- unlink() error on '%s': %s\n",
    164                     nameBuffer, strerror(errno));
    165             }
    166         }
    167     }
    168 
    169     closedir(dir);
    170 
    171     return 1;
    172 }
    173 
    174 static int write_int32(FILE* fh, int32_t val)
    175 {
    176     int res = fwrite(&val, 1, sizeof(val), fh);
    177     if (res != sizeof(val)) {
    178         fprintf(stderr, "unable to write int32 (%d bytes): %s\n", res, strerror(errno));
    179         return 0;
    180     }
    181 
    182     return 1;
    183 }
    184 
    185 static int write_int64(FILE* fh, int64_t val)
    186 {
    187     int res = fwrite(&val, 1, sizeof(val), fh);
    188     if (res != sizeof(val)) {
    189         fprintf(stderr, "unable to write int64 (%d bytes): %s\n", res, strerror(errno));
    190         return 0;
    191     }
    192 
    193     return 1;
    194 }
    195 
    196 static int copy_file(FILE* dest, FILE* src, off_t size, const char* destName,
    197         const char* srcName)
    198 {
    199     errno = 0;
    200 
    201     off_t origSize = size;
    202 
    203     while (size > 0) {
    204         int amt = size > (off_t)sizeof(copyBuffer) ? sizeof(copyBuffer) : (int)size;
    205         int readLen = fread(copyBuffer, 1, amt, src);
    206         if (readLen <= 0) {
    207             if (srcName != NULL) {
    208                 fprintf(stderr, "unable to read source (%d of %ld bytes) file '%s': %s\n",
    209                     amt, origSize, srcName, errno != 0 ? strerror(errno) : "unexpected EOF");
    210             } else {
    211                 fprintf(stderr, "unable to read buffer (%d of %ld bytes): %s\n",
    212                     amt, origSize, errno != 0 ? strerror(errno) : "unexpected EOF");
    213             }
    214             return 0;
    215         }
    216         int writeLen = fwrite(copyBuffer, 1, readLen, dest);
    217         if (writeLen != readLen) {
    218             if (destName != NULL) {
    219                 fprintf(stderr, "unable to write file (%d of %d bytes) '%s': '%s'\n",
    220                     writeLen, readLen, destName, strerror(errno));
    221             } else {
    222                 fprintf(stderr, "unable to write buffer (%d of %d bytes): '%s'\n",
    223                     writeLen, readLen, strerror(errno));
    224             }
    225             return 0;
    226         }
    227         size -= readLen;
    228     }
    229     return 1;
    230 }
    231 
    232 #define TYPE_END 0
    233 #define TYPE_DIR 1
    234 #define TYPE_FILE 2
    235 
    236 static int write_header(FILE* fh, int type, const char* path, const struct stat* st)
    237 {
    238     int pathLen = strlen(path);
    239     if (!write_int32(fh, type)) return 0;
    240     if (!write_int32(fh, pathLen)) return 0;
    241     if (fwrite(path, 1, pathLen, fh) != (size_t)pathLen) {
    242         fprintf(stderr, "unable to write: %s\n", strerror(errno));
    243         return 0;
    244     }
    245 
    246     if (!write_int32(fh, st->st_uid)) return 0;
    247     if (!write_int32(fh, st->st_gid)) return 0;
    248     if (!write_int32(fh, st->st_mode)) return 0;
    249     if (!write_int64(fh, ((int64_t)st->st_atime)*1000*1000*1000)) return 0;
    250     if (!write_int64(fh, ((int64_t)st->st_mtime)*1000*1000*1000)) return 0;
    251     if (!write_int64(fh, ((int64_t)st->st_ctime)*1000*1000*1000)) return 0;
    252 
    253     return 1;
    254 }
    255 
    256 static int backup_dir(FILE* fh, const char* srcPath)
    257 {
    258     DIR *dir;
    259     struct dirent *de;
    260     char* fullPath = NULL;
    261     int srcLen = strlen(srcPath);
    262     int result = 1;
    263     int i;
    264 
    265     dir = opendir(srcPath);
    266 
    267     if (dir == NULL) {
    268         fprintf (stderr, "error opendir'ing '%s': %s\n",
    269                     srcPath, strerror(errno));
    270         return 0;
    271     }
    272 
    273     for (;;) {
    274         de = readdir(dir);
    275 
    276         if (de == NULL) {
    277             break;
    278         }
    279 
    280         if (0 == strcmp(de->d_name, ".")
    281                 || 0 == strcmp(de->d_name, "..")
    282                 || 0 == strcmp(de->d_name, "lost+found")
    283         ) {
    284             continue;
    285         }
    286 
    287         if (fullPath != NULL) {
    288             free(fullPath);
    289         }
    290         fullPath = (char*)malloc(srcLen + strlen(de->d_name) + 2);
    291         strcpy(fullPath, srcPath);
    292         fullPath[srcLen] = '/';
    293         strcpy(fullPath+srcLen+1, de->d_name);
    294 
    295         /* See if this is a path we should skip. */
    296         if (!opt_backupAll) {
    297             for (i = 0; SKIP_PATHS[i].path; i++) {
    298                 if (strcmp(SKIP_PATHS[i].path, fullPath) == 0) {
    299                     break;
    300                 }
    301             }
    302             if (SKIP_PATHS[i].path != NULL) {
    303                 continue;
    304             }
    305         }
    306 
    307         int ret = lstat(fullPath, &statBuffer);
    308 
    309         if (ret != 0) {
    310             fprintf(stderr, "stat() error on '%s': %s\n",
    311                     fullPath, strerror(errno));
    312             result = 0;
    313             goto done;
    314         }
    315 
    316         if(S_ISDIR(statBuffer.st_mode)) {
    317             printf("Saving dir %s...\n", fullPath);
    318 
    319             if (write_header(fh, TYPE_DIR, fullPath, &statBuffer) == 0) {
    320                 result = 0;
    321                 goto done;
    322             }
    323             if (backup_dir(fh, fullPath) == 0) {
    324                 result = 0;
    325                 goto done;
    326             }
    327         } else if (S_ISREG(statBuffer.st_mode)) {
    328             // Skip the backup file
    329             if (backupFilePath && strcmp(fullPath, backupFilePath) == 0) {
    330                 printf("Skipping backup file %s...\n", backupFilePath);
    331                 continue;
    332             } else {
    333                 printf("Saving file %s...\n", fullPath);
    334             }
    335             if (write_header(fh, TYPE_FILE, fullPath, &statBuffer) == 0) {
    336                 result = 0;
    337                 goto done;
    338             }
    339 
    340             off_t size = statBuffer.st_size;
    341             if (!write_int64(fh, size)) {
    342                 result = 0;
    343                 goto done;
    344             }
    345 
    346             FILE* src = fopen(fullPath, "r");
    347             if (src == NULL) {
    348                 fprintf(stderr, "unable to open source file '%s': %s\n",
    349                     fullPath, strerror(errno));
    350                 result = 0;
    351                 goto done;
    352             }
    353 
    354             int copyres = copy_file(fh, src, size, NULL, fullPath);
    355             fclose(src);
    356             if (!copyres) {
    357                 result = 0;
    358                 goto done;
    359             }
    360         }
    361     }
    362 
    363 done:
    364     if (fullPath != NULL) {
    365         free(fullPath);
    366     }
    367 
    368     closedir(dir);
    369 
    370     return result;
    371 }
    372 
    373 static int backup_data(const char* destPath)
    374 {
    375     int res = -1;
    376 
    377     FILE* fh = fopen(destPath, "w");
    378     if (fh == NULL) {
    379         fprintf(stderr, "unable to open destination '%s': %s\n",
    380                 destPath, strerror(errno));
    381         return -1;
    382     }
    383 
    384     printf("Backing up /data to %s...\n", destPath);
    385 
    386     // The path that shouldn't be backed up
    387     backupFilePath = strdup(destPath);
    388 
    389     if (!write_int32(fh, FILE_VERSION)) goto done;
    390     if (!write_int32(fh, opt_backupAll)) goto done;
    391     if (!backup_dir(fh, "/data")) goto done;
    392     if (!write_int32(fh, 0)) goto done;
    393 
    394     res = 0;
    395 
    396 done:
    397     if (fflush(fh) != 0) {
    398         fprintf(stderr, "error flushing destination '%s': %s\n",
    399             destPath, strerror(errno));
    400         res = -1;
    401         goto donedone;
    402     }
    403     if (fsync(fileno(fh)) != 0) {
    404         fprintf(stderr, "error syncing destination '%s': %s\n",
    405             destPath, strerror(errno));
    406         res = -1;
    407         goto donedone;
    408     }
    409     fclose(fh);
    410     sync();
    411 
    412 donedone:
    413     return res;
    414 }
    415 
    416 static int32_t read_int32(FILE* fh, int32_t defVal)
    417 {
    418     int32_t val;
    419     if (fread(&val, 1, sizeof(val), fh) != sizeof(val)) {
    420         fprintf(stderr, "unable to read: %s\n", strerror(errno));
    421         return defVal;
    422     }
    423 
    424     return val;
    425 }
    426 
    427 static int64_t read_int64(FILE* fh, int64_t defVal)
    428 {
    429     int64_t val;
    430     if (fread(&val, 1, sizeof(val), fh) != sizeof(val)) {
    431         fprintf(stderr, "unable to read: %s\n", strerror(errno));
    432         return defVal;
    433     }
    434 
    435     return val;
    436 }
    437 
    438 static int read_header(FILE* fh, int* type, char** path, struct stat* st)
    439 {
    440     *type = read_int32(fh, -1);
    441     if (*type == TYPE_END) {
    442         return 1;
    443     }
    444 
    445     if (*type < 0) {
    446         fprintf(stderr, "bad token %d in restore file\n", *type);
    447         return 0;
    448     }
    449 
    450     int32_t pathLen = read_int32(fh, -1);
    451     if (pathLen <= 0) {
    452         fprintf(stderr, "bad path length %d in restore file\n", pathLen);
    453         return 0;
    454     }
    455     char* readPath = (char*)malloc(pathLen+1);
    456     if (fread(readPath, 1, pathLen, fh) != (size_t)pathLen) {
    457         fprintf(stderr, "truncated path in restore file\n");
    458         free(readPath);
    459         return 0;
    460     }
    461     readPath[pathLen] = 0;
    462     *path = readPath;
    463 
    464     st->st_uid = read_int32(fh, -1);
    465     if (st->st_uid == (uid_t)-1) {
    466         fprintf(stderr, "bad uid in restore file at '%s'\n", readPath);
    467         return 0;
    468     }
    469     st->st_gid = read_int32(fh, -1);
    470     if (st->st_gid == (gid_t)-1) {
    471         fprintf(stderr, "bad gid in restore file at '%s'\n", readPath);
    472         return 0;
    473     }
    474     st->st_mode = read_int32(fh, -1);
    475     if (st->st_mode == (mode_t)-1) {
    476         fprintf(stderr, "bad mode in restore file at '%s'\n", readPath);
    477         return 0;
    478     }
    479     int64_t ltime = read_int64(fh, -1);
    480     if (ltime < 0) {
    481         fprintf(stderr, "bad atime in restore file at '%s'\n", readPath);
    482         return 0;
    483     }
    484     st->st_atime = (time_t)(ltime/1000/1000/1000);
    485     ltime = read_int64(fh, -1);
    486     if (ltime < 0) {
    487         fprintf(stderr, "bad mtime in restore file at '%s'\n", readPath);
    488         return 0;
    489     }
    490     st->st_mtime = (time_t)(ltime/1000/1000/1000);
    491     ltime = read_int64(fh, -1);
    492     if (ltime < 0) {
    493         fprintf(stderr, "bad ctime in restore file at '%s'\n", readPath);
    494         return 0;
    495     }
    496     st->st_ctime = (time_t)(ltime/1000/1000/1000);
    497 
    498     st->st_mode &= (S_IRWXU|S_IRWXG|S_IRWXO);
    499 
    500     return 1;
    501 }
    502 
    503 static int restore_data(const char* srcPath)
    504 {
    505     int res = -1;
    506 
    507     FILE* fh = fopen(srcPath, "r");
    508     if (fh == NULL) {
    509         fprintf(stderr, "Unable to open source '%s': %s\n",
    510                 srcPath, strerror(errno));
    511         return -1;
    512     }
    513 
    514     inputFileVersion = read_int32(fh, 0);
    515     if (inputFileVersion < FILE_VERSION_1 || inputFileVersion > FILE_VERSION) {
    516         fprintf(stderr, "Restore file has bad version: 0x%x\n", inputFileVersion);
    517         goto done;
    518     }
    519 
    520     if (inputFileVersion >= FILE_VERSION_2) {
    521         opt_backupAll = read_int32(fh, 0);
    522     } else {
    523         opt_backupAll = 0;
    524     }
    525 
    526     // The path that shouldn't be deleted
    527     backupFilePath = strdup(srcPath);
    528 
    529     printf("Wiping contents of /data...\n");
    530     if (!wipe("/data")) {
    531         goto done;
    532     }
    533 
    534     printf("Restoring from %s to /data...\n", srcPath);
    535 
    536     while (1) {
    537         int type;
    538         char* path = NULL;
    539         if (read_header(fh, &type, &path, &statBuffer) == 0) {
    540             goto done;
    541         }
    542         if (type == 0) {
    543             break;
    544         }
    545 
    546         const char* typeName = "?";
    547 
    548         if (type == TYPE_DIR) {
    549             typeName = "dir";
    550 
    551             printf("Restoring dir %s...\n", path);
    552 
    553             if (mkdir(path, statBuffer.st_mode) != 0) {
    554                 if (errno != EEXIST) {
    555                     fprintf(stderr, "unable to create directory '%s': %s\n",
    556                         path, strerror(errno));
    557                     free(path);
    558                     goto done;
    559                 }
    560             }
    561 
    562         } else if (type == TYPE_FILE) {
    563             typeName = "file";
    564             off_t size = read_int64(fh, -1);
    565             if (size < 0) {
    566                 fprintf(stderr, "bad file size %ld in restore file\n", size);
    567                 free(path);
    568                 goto done;
    569             }
    570 
    571             printf("Restoring file %s...\n", path);
    572 
    573             FILE* dest = fopen(path, "w");
    574             if (dest == NULL) {
    575                 fprintf(stderr, "unable to open destination file '%s': %s\n",
    576                     path, strerror(errno));
    577                 free(path);
    578                 goto done;
    579             }
    580 
    581             int copyres = copy_file(dest, fh, size, path, NULL);
    582             fclose(dest);
    583             if (!copyres) {
    584                 free(path);
    585                 goto done;
    586             }
    587 
    588         } else {
    589             fprintf(stderr, "unknown node type %d\n", type);
    590             goto done;
    591         }
    592 
    593         // Do this even for directories, since the dir may have already existed
    594         // so we need to make sure it gets the correct mode.
    595         if (chmod(path, statBuffer.st_mode&(S_IRWXU|S_IRWXG|S_IRWXO)) != 0) {
    596             fprintf(stderr, "unable to chmod destination %s '%s' to 0x%x: %s\n",
    597                 typeName, path, statBuffer.st_mode, strerror(errno));
    598             free(path);
    599             goto done;
    600         }
    601 
    602         if (chown(path, statBuffer.st_uid, statBuffer.st_gid) != 0) {
    603             fprintf(stderr, "unable to chown destination %s '%s' to uid %d / gid %d: %s\n",
    604                 typeName, path, (int)statBuffer.st_uid, (int)statBuffer.st_gid, strerror(errno));
    605             free(path);
    606             goto done;
    607         }
    608 
    609         struct utimbuf timbuf;
    610         timbuf.actime = statBuffer.st_atime;
    611         timbuf.modtime = statBuffer.st_mtime;
    612         if (utime(path, &timbuf) != 0) {
    613             fprintf(stderr, "unable to utime destination %s '%s': %s\n",
    614                 typeName, path, strerror(errno));
    615             free(path);
    616             goto done;
    617         }
    618 
    619 
    620         free(path);
    621     }
    622 
    623     res = 0;
    624 
    625 done:
    626     fclose(fh);
    627 
    628     return res;
    629 }
    630 
    631 static void show_help(const char *cmd)
    632 {
    633     fprintf(stderr,"Usage: %s COMMAND [options] [backup-file-path]\n", cmd);
    634 
    635     fprintf(stderr, "commands are:\n"
    636                     "  help            Show this help text.\n"
    637                     "  backup          Perform a backup of /data.\n"
    638                     "  restore         Perform a restore of /data.\n");
    639     fprintf(stderr, "options include:\n"
    640                     "  -h              Show this help text.\n"
    641                     "  -a              Backup all files.\n");
    642     fprintf(stderr, "\nThe %s command allows you to perform low-level\n"
    643                     "backup and restore of the /data partition.  This is\n"
    644                     "where all user data is kept, allowing for a fairly\n"
    645                     "complete restore of a device's state.  Note that\n"
    646                     "because this is low-level, it will only work across\n"
    647                     "builds of the same (or very similar) device software.\n",
    648                     cmd);
    649 }
    650 
    651 } /* namespace android */
    652 
    653 int main (int argc, char **argv)
    654 {
    655     int restore = 0;
    656 
    657     if (getuid() != AID_ROOT) {
    658         fprintf(stderr, "error -- %s must run as root\n", argv[0]);
    659         exit(-1);
    660     }
    661 
    662     if (argc < 2) {
    663         fprintf(stderr, "No command specified.\n");
    664         android::show_help(argv[0]);
    665         exit(-1);
    666     }
    667 
    668     if (0 == strcmp(argv[1], "restore")) {
    669         restore = 1;
    670     } else if (0 == strcmp(argv[1], "help")) {
    671         android::show_help(argv[0]);
    672         exit(0);
    673     } else if (0 != strcmp(argv[1], "backup")) {
    674         fprintf(stderr, "Unknown command: %s\n", argv[1]);
    675         android::show_help(argv[0]);
    676         exit(-1);
    677     }
    678 
    679     android::opt_backupAll = 0;
    680 
    681     optind = 2;
    682 
    683     for (;;) {
    684         int ret;
    685 
    686         ret = getopt(argc, argv, "ah");
    687 
    688         if (ret < 0) {
    689             break;
    690         }
    691 
    692         switch(ret) {
    693             case 'a':
    694                 android::opt_backupAll = 1;
    695                 if (restore) fprintf(stderr, "Warning: -a option ignored on restore\n");
    696                 break;
    697             case 'h':
    698                 android::show_help(argv[0]);
    699                 exit(0);
    700             break;
    701 
    702             default:
    703                 fprintf(stderr,"Unrecognized Option\n");
    704                 android::show_help(argv[0]);
    705                 exit(-1);
    706             break;
    707         }
    708     }
    709 
    710     const char* backupFile = "/sdcard/backup.dat";
    711 
    712     if (argc > optind) {
    713         backupFile = argv[optind];
    714         optind++;
    715         if (argc != optind) {
    716             fprintf(stderr, "Too many arguments\n");
    717             android::show_help(argv[0]);
    718             exit(-1);
    719         }
    720     }
    721 
    722     printf("Stopping system...\n");
    723     property_set("ctl.stop", "runtime");
    724     property_set("ctl.stop", "zygote");
    725     sleep(1);
    726 
    727     int res;
    728     if (restore) {
    729         res = android::restore_data(backupFile);
    730         if (res != 0) {
    731             // Don't restart system, since the data partition is hosed.
    732             return res;
    733         }
    734         printf("Restore complete!  Restarting system, cross your fingers...\n");
    735     } else {
    736         res = android::backup_data(backupFile);
    737         if (res == 0) {
    738             printf("Backup complete!  Restarting system...\n");
    739         } else {
    740             printf("Restarting system...\n");
    741         }
    742     }
    743 
    744     property_set("ctl.start", "zygote");
    745     property_set("ctl.start", "runtime");
    746 }
    747