fs_mgr.c revision 6c2c121386f5e19ed74dc8d706bcb1e6f65fc1ba
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/* TO DO: 18 * 1. Re-direct fsck output to the kernel log? 19 * 20 */ 21 22#include <stdio.h> 23#include <stdlib.h> 24#include <string.h> 25#include <unistd.h> 26#include <fcntl.h> 27#include <ctype.h> 28#include <sys/mount.h> 29#include <sys/stat.h> 30#include <errno.h> 31#include <sys/types.h> 32#include <sys/wait.h> 33#include <libgen.h> 34#include <time.h> 35 36#include <private/android_filesystem_config.h> 37#include <cutils/partition_utils.h> 38#include <cutils/properties.h> 39 40#include "fs_mgr_priv.h" 41 42#define KEY_LOC_PROP "ro.crypto.keyfile.userdata" 43#define KEY_IN_FOOTER "footer" 44 45#define E2FSCK_BIN "/system/bin/e2fsck" 46 47struct flag_list { 48 const char *name; 49 unsigned flag; 50}; 51 52static struct flag_list mount_flags[] = { 53 { "noatime", MS_NOATIME }, 54 { "noexec", MS_NOEXEC }, 55 { "nosuid", MS_NOSUID }, 56 { "nodev", MS_NODEV }, 57 { "nodiratime", MS_NODIRATIME }, 58 { "ro", MS_RDONLY }, 59 { "rw", 0 }, 60 { "remount", MS_REMOUNT }, 61 { "bind", MS_BIND }, 62 { "rec", MS_REC }, 63 { "unbindable", MS_UNBINDABLE }, 64 { "private", MS_PRIVATE }, 65 { "slave", MS_SLAVE }, 66 { "shared", MS_SHARED }, 67 { "defaults", 0 }, 68 { 0, 0 }, 69}; 70 71static struct flag_list fs_mgr_flags[] = { 72 { "wait", MF_WAIT }, 73 { "check", MF_CHECK }, 74 { "encryptable=",MF_CRYPT }, 75 { "nonremovable",MF_NONREMOVABLE }, 76 { "voldmanaged=",MF_VOLDMANAGED}, 77 { "length=", MF_LENGTH }, 78 { "recoveryonly",MF_RECOVERYONLY }, 79 { "defaults", 0 }, 80 { 0, 0 }, 81}; 82 83/* 84 * gettime() - returns the time in seconds of the system's monotonic clock or 85 * zero on error. 86 */ 87static time_t gettime(void) 88{ 89 struct timespec ts; 90 int ret; 91 92 ret = clock_gettime(CLOCK_MONOTONIC, &ts); 93 if (ret < 0) { 94 ERROR("clock_gettime(CLOCK_MONOTONIC) failed: %s\n", strerror(errno)); 95 return 0; 96 } 97 98 return ts.tv_sec; 99} 100 101static int wait_for_file(const char *filename, int timeout) 102{ 103 struct stat info; 104 time_t timeout_time = gettime() + timeout; 105 int ret = -1; 106 107 while (gettime() < timeout_time && ((ret = stat(filename, &info)) < 0)) 108 usleep(10000); 109 110 return ret; 111} 112 113static int parse_flags(char *flags, struct flag_list *fl, 114 char **key_loc, long long *part_length, char **label, int *partnum, 115 char *fs_options, int fs_options_len) 116{ 117 int f = 0; 118 int i; 119 char *p; 120 char *savep; 121 122 /* initialize key_loc to null, if we find an MF_CRYPT flag, 123 * then we'll set key_loc to the proper value */ 124 if (key_loc) { 125 *key_loc = NULL; 126 } 127 /* initialize part_length to 0, if we find an MF_LENGTH flag, 128 * then we'll set part_length to the proper value */ 129 if (part_length) { 130 *part_length = 0; 131 } 132 if (partnum) { 133 *partnum = -1; 134 } 135 if (label) { 136 *label = NULL; 137 } 138 139 /* initialize fs_options to the null string */ 140 if (fs_options && (fs_options_len > 0)) { 141 fs_options[0] = '\0'; 142 } 143 144 p = strtok_r(flags, ",", &savep); 145 while (p) { 146 /* Look for the flag "p" in the flag list "fl" 147 * If not found, the loop exits with fl[i].name being null. 148 */ 149 for (i = 0; fl[i].name; i++) { 150 if (!strncmp(p, fl[i].name, strlen(fl[i].name))) { 151 f |= fl[i].flag; 152 if ((fl[i].flag == MF_CRYPT) && key_loc) { 153 /* The encryptable flag is followed by an = and the 154 * location of the keys. Get it and return it. 155 */ 156 *key_loc = strdup(strchr(p, '=') + 1); 157 } else if ((fl[i].flag == MF_LENGTH) && part_length) { 158 /* The length flag is followed by an = and the 159 * size of the partition. Get it and return it. 160 */ 161 *part_length = strtoll(strchr(p, '=') + 1, NULL, 0); 162 } else if ((fl[i].flag == MF_VOLDMANAGED) && label && partnum) { 163 /* The voldmanaged flag is followed by an = and the 164 * label, a colon and the partition number or the 165 * word "auto", e.g. 166 * voldmanaged=sdcard:3 167 * Get and return them. 168 */ 169 char *label_start; 170 char *label_end; 171 char *part_start; 172 173 label_start = strchr(p, '=') + 1; 174 label_end = strchr(p, ':'); 175 if (label_end) { 176 *label = strndup(label_start, 177 (int) (label_end - label_start)); 178 part_start = strchr(p, ':') + 1; 179 if (!strcmp(part_start, "auto")) { 180 *partnum = -1; 181 } else { 182 *partnum = strtol(part_start, NULL, 0); 183 } 184 } else { 185 ERROR("Warning: voldmanaged= flag malformed\n"); 186 } 187 } 188 break; 189 } 190 } 191 192 if (!fl[i].name) { 193 if (fs_options) { 194 /* It's not a known flag, so it must be a filesystem specific 195 * option. Add it to fs_options if it was passed in. 196 */ 197 strlcat(fs_options, p, fs_options_len); 198 strlcat(fs_options, ",", fs_options_len); 199 } else { 200 /* fs_options was not passed in, so if the flag is unknown 201 * it's an error. 202 */ 203 ERROR("Warning: unknown flag %s\n", p); 204 } 205 } 206 p = strtok_r(NULL, ",", &savep); 207 } 208 209out: 210 if (fs_options && fs_options[0]) { 211 /* remove the last trailing comma from the list of options */ 212 fs_options[strlen(fs_options) - 1] = '\0'; 213 } 214 215 return f; 216} 217 218/* Read a line of text till the next newline character. 219 * If no newline is found before the buffer is full, continue reading till a new line is seen, 220 * then return an empty buffer. This effectively ignores lines that are too long. 221 * On EOF, return null. 222 */ 223static char *fs_getline(char *buf, int size, FILE *file) 224{ 225 int cnt = 0; 226 int eof = 0; 227 int eol = 0; 228 int c; 229 230 if (size < 1) { 231 return NULL; 232 } 233 234 while (cnt < (size - 1)) { 235 c = getc(file); 236 if (c == EOF) { 237 eof = 1; 238 break; 239 } 240 241 *(buf + cnt) = c; 242 cnt++; 243 244 if (c == '\n') { 245 eol = 1; 246 break; 247 } 248 } 249 250 /* Null terminate what we've read */ 251 *(buf + cnt) = '\0'; 252 253 if (eof) { 254 if (cnt) { 255 return buf; 256 } else { 257 return NULL; 258 } 259 } else if (eol) { 260 return buf; 261 } else { 262 /* The line is too long. Read till a newline or EOF. 263 * If EOF, return null, if newline, return an empty buffer. 264 */ 265 while(1) { 266 c = getc(file); 267 if (c == EOF) { 268 return NULL; 269 } else if (c == '\n') { 270 *buf = '\0'; 271 return buf; 272 } 273 } 274 } 275} 276 277struct fstab *fs_mgr_read_fstab(const char *fstab_path) 278{ 279 FILE *fstab_file; 280 int cnt, entries; 281 int len; 282 char line[256]; 283 const char *delim = " \t"; 284 char *save_ptr, *p; 285 struct fstab *fstab; 286 struct fstab_rec *recs; 287 char *key_loc; 288 long long part_length; 289 char *label; 290 int partnum; 291#define FS_OPTIONS_LEN 1024 292 char tmp_fs_options[FS_OPTIONS_LEN]; 293 294 fstab_file = fopen(fstab_path, "r"); 295 if (!fstab_file) { 296 ERROR("Cannot open file %s\n", fstab_path); 297 return 0; 298 } 299 300 entries = 0; 301 while (fs_getline(line, sizeof(line), fstab_file)) { 302 /* if the last character is a newline, shorten the string by 1 byte */ 303 len = strlen(line); 304 if (line[len - 1] == '\n') { 305 line[len - 1] = '\0'; 306 } 307 /* Skip any leading whitespace */ 308 p = line; 309 while (isspace(*p)) { 310 p++; 311 } 312 /* ignore comments or empty lines */ 313 if (*p == '#' || *p == '\0') 314 continue; 315 entries++; 316 } 317 318 if (!entries) { 319 ERROR("No entries found in fstab\n"); 320 return 0; 321 } 322 323 /* Allocate and init the fstab structure */ 324 fstab = calloc(1, sizeof(struct fstab)); 325 fstab->num_entries = entries; 326 fstab->fstab_filename = strdup(fstab_path); 327 fstab->recs = calloc(fstab->num_entries, sizeof(struct fstab_rec)); 328 329 fseek(fstab_file, 0, SEEK_SET); 330 331 cnt = 0; 332 while (fs_getline(line, sizeof(line), fstab_file)) { 333 /* if the last character is a newline, shorten the string by 1 byte */ 334 len = strlen(line); 335 if (line[len - 1] == '\n') { 336 line[len - 1] = '\0'; 337 } 338 339 /* Skip any leading whitespace */ 340 p = line; 341 while (isspace(*p)) { 342 p++; 343 } 344 /* ignore comments or empty lines */ 345 if (*p == '#' || *p == '\0') 346 continue; 347 348 /* If a non-comment entry is greater than the size we allocated, give an 349 * error and quit. This can happen in the unlikely case the file changes 350 * between the two reads. 351 */ 352 if (cnt >= entries) { 353 ERROR("Tried to process more entries than counted\n"); 354 break; 355 } 356 357 if (!(p = strtok_r(line, delim, &save_ptr))) { 358 ERROR("Error parsing mount source\n"); 359 return 0; 360 } 361 fstab->recs[cnt].blk_device = strdup(p); 362 363 if (!(p = strtok_r(NULL, delim, &save_ptr))) { 364 ERROR("Error parsing mount_point\n"); 365 return 0; 366 } 367 fstab->recs[cnt].mount_point = strdup(p); 368 369 if (!(p = strtok_r(NULL, delim, &save_ptr))) { 370 ERROR("Error parsing fs_type\n"); 371 return 0; 372 } 373 fstab->recs[cnt].fs_type = strdup(p); 374 375 if (!(p = strtok_r(NULL, delim, &save_ptr))) { 376 ERROR("Error parsing mount_flags\n"); 377 return 0; 378 } 379 tmp_fs_options[0] = '\0'; 380 fstab->recs[cnt].flags = parse_flags(p, mount_flags, 381 NULL, NULL, NULL, NULL, 382 tmp_fs_options, FS_OPTIONS_LEN); 383 384 /* fs_options are optional */ 385 if (tmp_fs_options[0]) { 386 fstab->recs[cnt].fs_options = strdup(tmp_fs_options); 387 } else { 388 fstab->recs[cnt].fs_options = NULL; 389 } 390 391 if (!(p = strtok_r(NULL, delim, &save_ptr))) { 392 ERROR("Error parsing fs_mgr_options\n"); 393 return 0; 394 } 395 fstab->recs[cnt].fs_mgr_flags = parse_flags(p, fs_mgr_flags, 396 &key_loc, &part_length, 397 &label, &partnum, 398 NULL, 0); 399 fstab->recs[cnt].key_loc = key_loc; 400 fstab->recs[cnt].length = part_length; 401 fstab->recs[cnt].label = label; 402 fstab->recs[cnt].partnum = partnum; 403 cnt++; 404 } 405 fclose(fstab_file); 406 407 return fstab; 408} 409 410void fs_mgr_free_fstab(struct fstab *fstab) 411{ 412 int i; 413 414 for (i = 0; i < fstab->num_entries; i++) { 415 /* Free the pointers return by strdup(3) */ 416 free(fstab->recs[i].blk_device); 417 free(fstab->recs[i].mount_point); 418 free(fstab->recs[i].fs_type); 419 free(fstab->recs[i].fs_options); 420 free(fstab->recs[i].key_loc); 421 free(fstab->recs[i].label); 422 i++; 423 } 424 425 /* Free the fstab_recs array created by calloc(3) */ 426 free(fstab->recs); 427 428 /* Free the fstab filename */ 429 free(fstab->fstab_filename); 430 431 /* Free fstab */ 432 free(fstab); 433} 434 435static void check_fs(char *blk_device, char *fs_type, char *target) 436{ 437 pid_t pid; 438 int status; 439 int ret; 440 long tmpmnt_flags = MS_NOATIME | MS_NOEXEC | MS_NOSUID; 441 char *tmpmnt_opts = "nomblk_io_submit,errors=remount-ro"; 442 443 /* Check for the types of filesystems we know how to check */ 444 if (!strcmp(fs_type, "ext2") || !strcmp(fs_type, "ext3") || !strcmp(fs_type, "ext4")) { 445 /* 446 * First try to mount and unmount the filesystem. We do this because 447 * the kernel is more efficient than e2fsck in running the journal and 448 * processing orphaned inodes, and on at least one device with a 449 * performance issue in the emmc firmware, it can take e2fsck 2.5 minutes 450 * to do what the kernel does in about a second. 451 * 452 * After mounting and unmounting the filesystem, run e2fsck, and if an 453 * error is recorded in the filesystem superblock, e2fsck will do a full 454 * check. Otherwise, it does nothing. If the kernel cannot mount the 455 * filesytsem due to an error, e2fsck is still run to do a full check 456 * fix the filesystem. 457 */ 458 ret = mount(blk_device, target, fs_type, tmpmnt_flags, tmpmnt_opts); 459 if (!ret) { 460 umount(target); 461 } 462 463 INFO("Running %s on %s\n", E2FSCK_BIN, blk_device); 464 pid = fork(); 465 if (pid > 0) { 466 /* Parent, wait for the child to return */ 467 waitpid(pid, &status, 0); 468 } else if (pid == 0) { 469 /* child, run checker */ 470 execlp(E2FSCK_BIN, E2FSCK_BIN, "-y", blk_device, (char *)NULL); 471 472 /* Only gets here on error */ 473 ERROR("Cannot run fs_mgr binary %s\n", E2FSCK_BIN); 474 } else { 475 /* No need to check for error in fork, we can't really handle it now */ 476 ERROR("Fork failed trying to run %s\n", E2FSCK_BIN); 477 } 478 } 479 480 return; 481} 482 483static void remove_trailing_slashes(char *n) 484{ 485 int len; 486 487 len = strlen(n) - 1; 488 while ((*(n + len) == '/') && len) { 489 *(n + len) = '\0'; 490 len--; 491 } 492} 493 494static int fs_match(char *in1, char *in2) 495{ 496 char *n1; 497 char *n2; 498 int ret; 499 500 n1 = strdup(in1); 501 n2 = strdup(in2); 502 503 remove_trailing_slashes(n1); 504 remove_trailing_slashes(n2); 505 506 ret = !strcmp(n1, n2); 507 508 free(n1); 509 free(n2); 510 511 return ret; 512} 513 514int fs_mgr_mount_all(struct fstab *fstab) 515{ 516 int i = 0; 517 int encrypted = 0; 518 int ret = -1; 519 int mret; 520 521 if (!fstab) { 522 return ret; 523 } 524 525 for (i = 0; i < fstab->num_entries; i++) { 526 /* Don't mount entries that are managed by vold */ 527 if (fstab->recs[i].fs_mgr_flags & (MF_VOLDMANAGED | MF_RECOVERYONLY)) { 528 continue; 529 } 530 531 /* Skip raw partition entries such as boot, recovery, etc */ 532 if (!strcmp(fstab->recs[i].fs_type, "emmc") || 533 !strcmp(fstab->recs[i].fs_type, "mtd")) { 534 continue; 535 } 536 537 if (fstab->recs[i].fs_mgr_flags & MF_WAIT) { 538 wait_for_file(fstab->recs[i].blk_device, WAIT_TIMEOUT); 539 } 540 541 if (fstab->recs[i].fs_mgr_flags & MF_CHECK) { 542 check_fs(fstab->recs[i].blk_device, fstab->recs[i].fs_type, 543 fstab->recs[i].mount_point); 544 } 545 546 mret = mount(fstab->recs[i].blk_device, fstab->recs[i].mount_point, 547 fstab->recs[i].fs_type, fstab->recs[i].flags, 548 fstab->recs[i].fs_options); 549 if (!mret) { 550 /* Success! Go get the next one */ 551 continue; 552 } 553 554 /* mount(2) returned an error, check if it's encrypted and deal with it */ 555 if ((fstab->recs[i].fs_mgr_flags & MF_CRYPT) && 556 !partition_wiped(fstab->recs[i].blk_device)) { 557 /* Need to mount a tmpfs at this mountpoint for now, and set 558 * properties that vold will query later for decrypting 559 */ 560 if (mount("tmpfs", fstab->recs[i].mount_point, "tmpfs", 561 MS_NOATIME | MS_NOSUID | MS_NODEV, CRYPTO_TMPFS_OPTIONS) < 0) { 562 ERROR("Cannot mount tmpfs filesystem for encrypted fs at %s\n", 563 fstab->recs[i].mount_point); 564 goto out; 565 } 566 encrypted = 1; 567 } else { 568 ERROR("Cannot mount filesystem on %s at %s\n", 569 fstab->recs[i].blk_device, fstab->recs[i].mount_point); 570 goto out; 571 } 572 } 573 574 if (encrypted) { 575 ret = 1; 576 } else { 577 ret = 0; 578 } 579 580out: 581 return ret; 582} 583 584/* If tmp_mount_point is non-null, mount the filesystem there. This is for the 585 * tmp mount we do to check the user password 586 */ 587int fs_mgr_do_mount(struct fstab *fstab, char *n_name, char *n_blk_device, 588 char *tmp_mount_point) 589{ 590 int i = 0; 591 int ret = -1; 592 char *m; 593 594 if (!fstab) { 595 return ret; 596 } 597 598 for (i = 0; i < fstab->num_entries; i++) { 599 if (!fs_match(fstab->recs[i].mount_point, n_name)) { 600 continue; 601 } 602 603 /* We found our match */ 604 /* If this is a raw partition, report an error */ 605 if (!strcmp(fstab->recs[i].fs_type, "emmc") || 606 !strcmp(fstab->recs[i].fs_type, "mtd")) { 607 ERROR("Cannot mount filesystem of type %s on %s\n", 608 fstab->recs[i].fs_type, n_blk_device); 609 goto out; 610 } 611 612 /* First check the filesystem if requested */ 613 if (fstab->recs[i].fs_mgr_flags & MF_WAIT) { 614 wait_for_file(n_blk_device, WAIT_TIMEOUT); 615 } 616 617 if (fstab->recs[i].fs_mgr_flags & MF_CHECK) { 618 check_fs(n_blk_device, fstab->recs[i].fs_type, 619 fstab->recs[i].mount_point); 620 } 621 622 /* Now mount it where requested */ 623 if (tmp_mount_point) { 624 m = tmp_mount_point; 625 } else { 626 m = fstab->recs[i].mount_point; 627 } 628 if (mount(n_blk_device, m, fstab->recs[i].fs_type, 629 fstab->recs[i].flags, fstab->recs[i].fs_options)) { 630 ERROR("Cannot mount filesystem on %s at %s\n", 631 n_blk_device, m); 632 goto out; 633 } else { 634 ret = 0; 635 goto out; 636 } 637 } 638 639 /* We didn't find a match, say so and return an error */ 640 ERROR("Cannot find mount point %s in fstab\n", fstab->recs[i].mount_point); 641 642out: 643 return ret; 644} 645 646/* 647 * mount a tmpfs filesystem at the given point. 648 * return 0 on success, non-zero on failure. 649 */ 650int fs_mgr_do_tmpfs_mount(char *n_name) 651{ 652 int ret; 653 654 ret = mount("tmpfs", n_name, "tmpfs", 655 MS_NOATIME | MS_NOSUID | MS_NODEV, CRYPTO_TMPFS_OPTIONS); 656 if (ret < 0) { 657 ERROR("Cannot mount tmpfs filesystem at %s\n", n_name); 658 return -1; 659 } 660 661 /* Success */ 662 return 0; 663} 664 665int fs_mgr_unmount_all(struct fstab *fstab) 666{ 667 int i = 0; 668 int ret = 0; 669 670 if (!fstab) { 671 return -1; 672 } 673 674 while (fstab->recs[i].blk_device) { 675 if (umount(fstab->recs[i].mount_point)) { 676 ERROR("Cannot unmount filesystem at %s\n", fstab->recs[i].mount_point); 677 ret = -1; 678 } 679 i++; 680 } 681 682 return ret; 683} 684/* 685 * key_loc must be at least PROPERTY_VALUE_MAX bytes long 686 * 687 * real_blk_device must be at least PROPERTY_VALUE_MAX bytes long 688 */ 689int fs_mgr_get_crypt_info(struct fstab *fstab, char *key_loc, char *real_blk_device, int size) 690{ 691 int i = 0; 692 693 if (!fstab) { 694 return -1; 695 } 696 /* Initialize return values to null strings */ 697 if (key_loc) { 698 *key_loc = '\0'; 699 } 700 if (real_blk_device) { 701 *real_blk_device = '\0'; 702 } 703 704 /* Look for the encryptable partition to find the data */ 705 for (i = 0; i < fstab->num_entries; i++) { 706 /* Don't deal with vold managed enryptable partitions here */ 707 if (fstab->recs[i].fs_mgr_flags & MF_VOLDMANAGED) { 708 continue; 709 } 710 if (!(fstab->recs[i].fs_mgr_flags & MF_CRYPT)) { 711 continue; 712 } 713 714 /* We found a match */ 715 if (key_loc) { 716 strlcpy(key_loc, fstab->recs[i].key_loc, size); 717 } 718 if (real_blk_device) { 719 strlcpy(real_blk_device, fstab->recs[i].blk_device, size); 720 } 721 break; 722 } 723 724 return 0; 725} 726 727/* Add an entry to the fstab, and return 0 on success or -1 on error */ 728int fs_mgr_add_entry(struct fstab *fstab, 729 const char *mount_point, const char *fs_type, 730 const char *blk_device, long long length) 731{ 732 struct fstab_rec *new_fstab_recs; 733 int n = fstab->num_entries; 734 735 new_fstab_recs = (struct fstab_rec *) 736 realloc(fstab->recs, sizeof(struct fstab_rec) * (n + 1)); 737 738 if (!new_fstab_recs) { 739 return -1; 740 } 741 742 /* A new entry was added, so initialize it */ 743 memset(&new_fstab_recs[n], 0, sizeof(struct fstab_rec)); 744 new_fstab_recs[n].mount_point = strdup(mount_point); 745 new_fstab_recs[n].fs_type = strdup(fs_type); 746 new_fstab_recs[n].blk_device = strdup(blk_device); 747 new_fstab_recs[n].length = 0; 748 749 /* Update the fstab struct */ 750 fstab->recs = new_fstab_recs; 751 fstab->num_entries++; 752 753 return 0; 754} 755 756struct fstab_rec *fs_mgr_get_entry_for_mount_point(struct fstab *fstab, const char *path) 757{ 758 int i; 759 760 if (!fstab) { 761 return NULL; 762 } 763 764 for (i = 0; i < fstab->num_entries; i++) { 765 int len = strlen(fstab->recs[i].mount_point); 766 if (strncmp(path, fstab->recs[i].mount_point, len) == 0 && 767 (path[len] == '\0' || path[len] == '/')) { 768 return &fstab->recs[i]; 769 } 770 } 771 772 return NULL; 773} 774 775int fs_mgr_is_voldmanaged(struct fstab_rec *fstab) 776{ 777 return fstab->fs_mgr_flags & MF_VOLDMANAGED; 778} 779 780int fs_mgr_is_nonremovable(struct fstab_rec *fstab) 781{ 782 return fstab->fs_mgr_flags & MF_NONREMOVABLE; 783} 784 785int fs_mgr_is_encryptable(struct fstab_rec *fstab) 786{ 787 return fstab->fs_mgr_flags & MF_CRYPT; 788} 789 790