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
parse_len(const char * str,uint64_t * plen)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
load_partitions(cnode * root,struct disk_info * dinfo)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 *
load_diskconfig(const char * fn,char * path_override)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
sync_ptable(int fd)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
validate(struct disk_info * dinfo)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
validate_and_config(struct disk_info * dinfo,int * fd,struct write_list ** lst)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
process_disk_config(struct disk_info * dinfo)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
apply_disk_config(struct disk_info * dinfo,int test)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
dump_disk_config(struct disk_info * dinfo)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 *
find_part(struct disk_info * dinfo,const char * name)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 *
find_part_device(struct disk_info * dinfo,const char * name)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