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