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