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