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