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, §_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