Home | History | Annotate | Download | only in libdiskconfig
      1 /* libs/diskconfig/diskconfig.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 "diskconfig"
     19 
     20 #include <errno.h>
     21 #include <fcntl.h>
     22 #include <stdio.h>
     23 #include <stdlib.h>
     24 #include <string.h>
     25 #include <unistd.h>
     26 #include <sys/ioctl.h>
     27 #include <sys/stat.h>
     28 
     29 #include <linux/fs.h>
     30 
     31 #include <cutils/config_utils.h>
     32 #include <cutils/log.h>
     33 
     34 #include <diskconfig/diskconfig.h>
     35 
     36 
     37 static int
     38 parse_len(const char *str, uint64_t *plen)
     39 {
     40     char tmp[64];
     41     int len_str;
     42     uint32_t multiple = 1;
     43 
     44     strncpy(tmp, str, sizeof(tmp));
     45     tmp[sizeof(tmp)-1] = '\0';
     46     len_str = strlen(tmp);
     47     if (!len_str) {
     48         ALOGE("Invalid disk length specified.");
     49         return 1;
     50     }
     51 
     52     switch(tmp[len_str - 1]) {
     53         case 'M': case 'm':
     54             /* megabyte */
     55             multiple <<= 10;
     56         case 'K': case 'k':
     57             /* kilobytes */
     58             multiple <<= 10;
     59             tmp[len_str - 1] = '\0';
     60             break;
     61         default:
     62             break;
     63     }
     64 
     65     *plen = strtoull(tmp, NULL, 0);
     66     if (!*plen) {
     67         ALOGE("Invalid length specified: %s", str);
     68         return 1;
     69     }
     70 
     71     if (*plen == (uint64_t)-1) {
     72         if (multiple > 1) {
     73             ALOGE("Size modifier illegal when len is -1");
     74             return 1;
     75         }
     76     } else {
     77         /* convert len to kilobytes */
     78         if (multiple > 1024)
     79             multiple >>= 10;
     80         *plen *= multiple;
     81 
     82         if (*plen > 0xffffffffULL) {
     83             ALOGE("Length specified is too large!: %llu KB", *plen);
     84             return 1;
     85         }
     86     }
     87 
     88     return 0;
     89 }
     90 
     91 
     92 static int
     93 load_partitions(cnode *root, struct disk_info *dinfo)
     94 {
     95     cnode *partnode;
     96 
     97     dinfo->num_parts = 0;
     98     for (partnode = root->first_child; partnode; partnode = partnode->next) {
     99         struct part_info *pinfo = &dinfo->part_lst[dinfo->num_parts];
    100         const char *tmp;
    101 
    102         /* bleh, i will leak memory here, but i DONT CARE since
    103          * the only right thing to do when this function fails
    104          * is to quit */
    105         pinfo->name = strdup(partnode->name);
    106 
    107         if(config_bool(partnode, "active", 0))
    108             pinfo->flags |= PART_ACTIVE_FLAG;
    109 
    110         if (!(tmp = config_str(partnode, "type", NULL))) {
    111             ALOGE("Partition type required: %s", pinfo->name);
    112             return 1;
    113         }
    114 
    115         /* possible values are: linux, fat32 */
    116         if (!strcmp(tmp, "linux")) {
    117             pinfo->type = PC_PART_TYPE_LINUX;
    118         } else if (!strcmp(tmp, "fat32")) {
    119             pinfo->type = PC_PART_TYPE_FAT32;
    120         } else {
    121             ALOGE("Unsupported partition type found: %s", tmp);
    122             return 1;
    123         }
    124 
    125         if ((tmp = config_str(partnode, "len", NULL)) != NULL) {
    126             uint64_t len;
    127             if (parse_len(tmp, &len))
    128                 return 1;
    129             pinfo->len_kb = (uint32_t) len;
    130         } else
    131             pinfo->len_kb = 0;
    132 
    133         ++dinfo->num_parts;
    134     }
    135 
    136     return 0;
    137 }
    138 
    139 struct disk_info *
    140 load_diskconfig(const char *fn, char *path_override)
    141 {
    142     struct disk_info *dinfo;
    143     cnode *devroot;
    144     cnode *partnode;
    145     cnode *root = config_node("", "");
    146     const char *tmp;
    147 
    148     if (!(dinfo = malloc(sizeof(struct disk_info)))) {
    149         ALOGE("Could not malloc disk_info");
    150         return NULL;
    151     }
    152     memset(dinfo, 0, sizeof(struct disk_info));
    153 
    154     if (!(dinfo->part_lst = malloc(MAX_NUM_PARTS * sizeof(struct part_info)))) {
    155         ALOGE("Could not malloc part_lst");
    156         goto fail;
    157     }
    158     memset(dinfo->part_lst, 0,
    159            (MAX_NUM_PARTS * sizeof(struct part_info)));
    160 
    161     config_load_file(root, fn);
    162     if (root->first_child == NULL) {
    163         ALOGE("Could not read config file %s", fn);
    164         goto fail;
    165     }
    166 
    167     if (!(devroot = config_find(root, "device"))) {
    168         ALOGE("Could not find device section in config file '%s'", fn);
    169         goto fail;
    170     }
    171 
    172 
    173     if (!(tmp = config_str(devroot, "path", path_override))) {
    174         ALOGE("device path is requried");
    175         goto fail;
    176     }
    177     dinfo->device = strdup(tmp);
    178 
    179     /* find the partition scheme */
    180     if (!(tmp = config_str(devroot, "scheme", NULL))) {
    181         ALOGE("partition scheme is required");
    182         goto fail;
    183     } else if (!strcmp(tmp, "mbr")) {
    184         dinfo->scheme = PART_SCHEME_MBR;
    185     } else if (!strcmp(tmp, "gpt")) {
    186         ALOGE("'gpt' partition scheme not supported yet.");
    187         goto fail;
    188     } else {
    189         ALOGE("Unknown partition scheme specified: %s", tmp);
    190         goto fail;
    191     }
    192 
    193     /* grab the sector size (in bytes) */
    194     tmp = config_str(devroot, "sector_size", "512");
    195     dinfo->sect_size = strtol(tmp, NULL, 0);
    196     if (!dinfo->sect_size) {
    197         ALOGE("Invalid sector size: %s", tmp);
    198         goto fail;
    199     }
    200 
    201     /* first lba where the partitions will start on disk */
    202     if (!(tmp = config_str(devroot, "start_lba", NULL))) {
    203         ALOGE("start_lba must be provided");
    204         goto fail;
    205     }
    206 
    207     if (!(dinfo->skip_lba = strtol(tmp, NULL, 0))) {
    208         ALOGE("Invalid starting LBA (or zero): %s", tmp);
    209         goto fail;
    210     }
    211 
    212     /* Number of LBAs on disk */
    213     if (!(tmp = config_str(devroot, "num_lba", NULL))) {
    214         ALOGE("num_lba is required");
    215         goto fail;
    216     }
    217     dinfo->num_lba = strtoul(tmp, NULL, 0);
    218 
    219     if (!(partnode = config_find(devroot, "partitions"))) {
    220         ALOGE("Device must specify partition list");
    221         goto fail;
    222     }
    223 
    224     if (load_partitions(partnode, dinfo))
    225         goto fail;
    226 
    227     return dinfo;
    228 
    229 fail:
    230     if (dinfo->part_lst)
    231         free(dinfo->part_lst);
    232     if (dinfo->device)
    233         free(dinfo->device);
    234     free(dinfo);
    235     return NULL;
    236 }
    237 
    238 static int
    239 sync_ptable(int fd)
    240 {
    241     struct stat stat;
    242     int rv;
    243 
    244     sync();
    245 
    246     if (fstat(fd, &stat)) {
    247        ALOGE("Cannot stat, errno=%d.", errno);
    248        return -1;
    249     }
    250 
    251     if (S_ISBLK(stat.st_mode) && ((rv = ioctl(fd, BLKRRPART, NULL)) < 0)) {
    252         ALOGE("Could not re-read partition table. REBOOT!. (errno=%d)", errno);
    253         return -1;
    254     }
    255 
    256     return 0;
    257 }
    258 
    259 /* This function verifies that the disk info provided is valid, and if so,
    260  * returns an open file descriptor.
    261  *
    262  * This does not necessarily mean that it will later be successfully written
    263  * though. If we use the pc-bios partitioning scheme, we must use extended
    264  * partitions, which eat up some hd space. If the user manually provisioned
    265  * every single partition, but did not account for the extra needed space,
    266  * then we will later fail.
    267  *
    268  * TODO: Make validation more complete.
    269  */
    270 static int
    271 validate(struct disk_info *dinfo)
    272 {
    273     int fd;
    274     int sect_sz;
    275     uint64_t disk_size;
    276     uint64_t total_size;
    277     int cnt;
    278     struct stat stat;
    279 
    280     if (!dinfo)
    281         return -1;
    282 
    283     if ((fd = open(dinfo->device, O_RDWR)) < 0) {
    284         ALOGE("Cannot open device '%s' (errno=%d)", dinfo->device, errno);
    285         return -1;
    286     }
    287 
    288     if (fstat(fd, &stat)) {
    289         ALOGE("Cannot stat file '%s', errno=%d.", dinfo->device, errno);
    290         goto fail;
    291     }
    292 
    293 
    294     /* XXX: Some of the code below is kind of redundant and should probably
    295      * be refactored a little, but it will do for now. */
    296 
    297     /* Verify that we can operate on the device that was requested.
    298      * We presently only support block devices and regular file images. */
    299     if (S_ISBLK(stat.st_mode)) {
    300         /* get the sector size and make sure we agree */
    301         if (ioctl(fd, BLKSSZGET, &sect_sz) < 0) {
    302             ALOGE("Cannot get sector size (errno=%d)", errno);
    303             goto fail;
    304         }
    305 
    306         if (!sect_sz || sect_sz != dinfo->sect_size) {
    307             ALOGE("Device sector size is zero or sector sizes do not match!");
    308             goto fail;
    309         }
    310 
    311         /* allow the user override the "disk size" if they provided num_lba */
    312         if (!dinfo->num_lba) {
    313             if (ioctl(fd, BLKGETSIZE64, &disk_size) < 0) {
    314                 ALOGE("Could not get block device size (errno=%d)", errno);
    315                 goto fail;
    316             }
    317             /* XXX: we assume that the disk has < 2^32 sectors :-) */
    318             dinfo->num_lba = (uint32_t)(disk_size / (uint64_t)dinfo->sect_size);
    319         } else
    320             disk_size = (uint64_t)dinfo->num_lba * (uint64_t)dinfo->sect_size;
    321     } else if (S_ISREG(stat.st_mode)) {
    322         ALOGI("Requesting operation on a regular file, not block device.");
    323         if (!dinfo->sect_size) {
    324             ALOGE("Sector size for regular file images cannot be zero");
    325             goto fail;
    326         }
    327         if (dinfo->num_lba)
    328             disk_size = (uint64_t)dinfo->num_lba * (uint64_t)dinfo->sect_size;
    329         else {
    330             dinfo->num_lba = (uint32_t)(stat.st_size / dinfo->sect_size);
    331             disk_size = (uint64_t)stat.st_size;
    332         }
    333     } else {
    334         ALOGE("Device does not refer to a regular file or a block device!");
    335         goto fail;
    336     }
    337 
    338 #if 1
    339     ALOGV("Device/file %s: size=%llu bytes, num_lba=%u, sect_size=%d",
    340          dinfo->device, disk_size, dinfo->num_lba, dinfo->sect_size);
    341 #endif
    342 
    343     /* since this is our offset into the disk, we start off with that as
    344      * our size of needed partitions */
    345     total_size = dinfo->skip_lba * dinfo->sect_size;
    346 
    347     /* add up all the partition sizes and make sure it fits */
    348     for (cnt = 0; cnt < dinfo->num_parts; ++cnt) {
    349         struct part_info *part = &dinfo->part_lst[cnt];
    350         if (part->len_kb != (uint32_t)-1) {
    351             total_size += part->len_kb * 1024;
    352         } else if (part->len_kb == 0) {
    353             ALOGE("Zero-size partition '%s' is invalid.", part->name);
    354             goto fail;
    355         } else {
    356             /* the partition requests the rest of the disk. */
    357             if (cnt + 1 != dinfo->num_parts) {
    358                 ALOGE("Only the last partition in the list can request to fill "
    359                      "the rest of disk.");
    360                 goto fail;
    361             }
    362         }
    363 
    364         if ((part->type != PC_PART_TYPE_LINUX) &&
    365             (part->type != PC_PART_TYPE_FAT32)) {
    366             ALOGE("Unknown partition type (0x%x) encountered for partition "
    367                  "'%s'\n", part->type, part->name);
    368             goto fail;
    369         }
    370     }
    371 
    372     /* only matters for disks, not files */
    373     if (S_ISBLK(stat.st_mode) && total_size > disk_size) {
    374         ALOGE("Total requested size of partitions (%llu) is greater than disk "
    375              "size (%llu).", total_size, disk_size);
    376         goto fail;
    377     }
    378 
    379     return fd;
    380 
    381 fail:
    382     close(fd);
    383     return -1;
    384 }
    385 
    386 static int
    387 validate_and_config(struct disk_info *dinfo, int *fd, struct write_list **lst)
    388 {
    389     *lst = NULL;
    390     *fd = -1;
    391 
    392     if ((*fd = validate(dinfo)) < 0)
    393         return 1;
    394 
    395     switch (dinfo->scheme) {
    396         case PART_SCHEME_MBR:
    397             *lst = config_mbr(dinfo);
    398             return *lst == NULL;
    399         case PART_SCHEME_GPT:
    400             /* not supported yet */
    401         default:
    402             ALOGE("Uknown partition scheme.");
    403             break;
    404     }
    405 
    406     close(*fd);
    407     *lst = NULL;
    408     return 1;
    409 }
    410 
    411 /* validate and process the disk layout configuration.
    412  * This will cause an update to the partitions' start lba.
    413  *
    414  * Basically, this does the same thing as apply_disk_config in test mode,
    415  * except that wlist_commit is not called to print out the data to be
    416  * written.
    417  */
    418 int
    419 process_disk_config(struct disk_info *dinfo)
    420 {
    421     struct write_list *lst;
    422     int fd;
    423 
    424     if (validate_and_config(dinfo, &fd, &lst) != 0)
    425         return 1;
    426 
    427     close(fd);
    428     wlist_free(lst);
    429     return 0;
    430 }
    431 
    432 
    433 int
    434 apply_disk_config(struct disk_info *dinfo, int test)
    435 {
    436     int fd;
    437     struct write_list *wr_lst = NULL;
    438     int rv;
    439 
    440     if (validate_and_config(dinfo, &fd, &wr_lst) != 0) {
    441         ALOGE("Configuration is invalid.");
    442         goto fail;
    443     }
    444 
    445     if ((rv = wlist_commit(fd, wr_lst, test)) >= 0)
    446         rv = test ? 0 : sync_ptable(fd);
    447 
    448     close(fd);
    449     wlist_free(wr_lst);
    450     return rv;
    451 
    452 fail:
    453     close(fd);
    454     if (wr_lst)
    455         wlist_free(wr_lst);
    456     return 1;
    457 }
    458 
    459 int
    460 dump_disk_config(struct disk_info *dinfo)
    461 {
    462     int cnt;
    463     struct part_info *part;
    464 
    465     printf("Device: %s\n", dinfo->device);
    466     printf("Scheme: ");
    467     switch (dinfo->scheme) {
    468         case PART_SCHEME_MBR:
    469             printf("MBR");
    470             break;
    471         case PART_SCHEME_GPT:
    472             printf("GPT (unsupported)");
    473             break;
    474         default:
    475             printf("Unknown");
    476             break;
    477     }
    478     printf ("\n");
    479 
    480     printf("Sector size: %d\n", dinfo->sect_size);
    481     printf("Skip leading LBAs: %u\n", dinfo->skip_lba);
    482     printf("Number of LBAs: %u\n", dinfo->num_lba);
    483     printf("Partitions:\n");
    484 
    485     for (cnt = 0; cnt < dinfo->num_parts; ++cnt) {
    486         part = &dinfo->part_lst[cnt];
    487         printf("\tname = %s\n", part->name);
    488         printf("\t\tflags = %s\n",
    489                part->flags & PART_ACTIVE_FLAG ? "Active" : "None");
    490         printf("\t\ttype = %s\n",
    491                part->type == PC_PART_TYPE_LINUX ? "Linux" : "Unknown");
    492         if (part->len_kb == (uint32_t)-1)
    493             printf("\t\tlen = rest of disk\n");
    494         else
    495             printf("\t\tlen = %uKB\n", part->len_kb);
    496     }
    497     printf("Total number of partitions: %d\n", cnt);
    498     printf("\n");
    499 
    500     return 0;
    501 }
    502 
    503 struct part_info *
    504 find_part(struct disk_info *dinfo, const char *name)
    505 {
    506     struct part_info *pinfo;
    507     int cnt;
    508 
    509     for (cnt = 0; cnt < dinfo->num_parts; ++cnt) {
    510         pinfo = &dinfo->part_lst[cnt];
    511         if (!strcmp(pinfo->name, name))
    512             return pinfo;
    513     }
    514 
    515     return NULL;
    516 }
    517 
    518 /* NOTE: If the returned ptr is non-NULL, it must be freed by the caller. */
    519 char *
    520 find_part_device(struct disk_info *dinfo, const char *name)
    521 {
    522     switch (dinfo->scheme) {
    523         case PART_SCHEME_MBR:
    524             return find_mbr_part(dinfo, name);
    525         case PART_SCHEME_GPT:
    526             ALOGE("GPT is presently not supported");
    527             break;
    528         default:
    529             ALOGE("Unknown partition table scheme");
    530             break;
    531     }
    532 
    533     return NULL;
    534 }
    535 
    536 
    537