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