| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| ** Android Secure External Cache |
| */ |
| |
| #include "mountd.h" |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <ctype.h> |
| #include <pwd.h> |
| #include <stdlib.h> |
| #include <poll.h> |
| #include <errno.h> |
| |
| #include <sys/ioctl.h> |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| |
| #include <linux/dm-ioctl.h> |
| #include <linux/loop.h> |
| |
| #include <cutils/properties.h> |
| #include <cutils/misc.h> |
| |
| #include "ASEC.h" |
| |
| //#define MODULE_FAILURE_IS_FATAL |
| |
| extern int init_module(void *, unsigned long, const char *); |
| extern int delete_module(const char *, unsigned int); |
| |
| struct asec_context |
| { |
| char *name; // Device mapper volume name |
| char *srcPath; // Path to the source (original) mount |
| char *backingFile; // Name of the image file |
| unsigned int sectors; // Number of sectors |
| char *dstPath; // Destination mount point |
| char *crypt; // Crypt options |
| |
| boolean needs_format; |
| boolean started; |
| int cacheFd; |
| int lo_num; |
| int dm_num; |
| unsigned char key[16]; |
| }; |
| |
| static const char *MODULES[] = { "dm_mod", "crypto", "crypto_algapi", "crypto_blkcipher", |
| "cryptomgr", "dm_crypt", "jbd", |
| "twofish_common", "twofish", "cbc", |
| "mbcache", "ext3", |
| NULL }; |
| static const char KEY_PATH[] = "/data/system/asec.key"; |
| static const char MODULE_PATH[] = "/system/lib/modules"; |
| static const char MKE2FS_PATH[] = "/system/bin/mke2fs"; |
| static const char E2FSCK_PATH[] = "/system/bin/e2fsck"; |
| |
| boolean AsecIsStarted(void *Handle) |
| { |
| struct asec_context *ctx = (struct asec_context *) Handle; |
| |
| return ctx->started; |
| } |
| |
| const char *AsecMountPoint(void *Handle) |
| { |
| struct asec_context *ctx = (struct asec_context *) Handle; |
| |
| return ctx->dstPath; |
| } |
| |
| static boolean AsecIsEnabled() |
| { |
| char value[PROPERTY_VALUE_MAX]; |
| int enabled; |
| |
| property_get(ASEC_ENABLED, value, "0"); |
| |
| if (atoi(value) == 1) |
| return true; |
| return false; |
| } |
| |
| void *AsecInit(const char *Name, const char *SrcPath, const char *BackingFile, |
| const char *Size, const char *DstPath, const char *Crypt) |
| { |
| struct asec_context *ctx; |
| |
| if (!AsecIsEnabled()) |
| return NULL; |
| |
| LOG_ASEC("AsecInit(%s, %s, %s, %s, %s, %s):\n", |
| Name, SrcPath, BackingFile, Size, DstPath, Crypt); |
| |
| if (!Name || !SrcPath || !BackingFile || !Size || !DstPath || !Crypt) { |
| LOG_ERROR("AsecInit(): Invalid arguments\n"); |
| return NULL; |
| } |
| |
| if (!(ctx = malloc(sizeof(struct asec_context)))) { |
| LOG_ERROR("AsecInit(): Out of memory\n"); |
| return NULL; |
| } |
| |
| memset(ctx, 0, sizeof(struct asec_context)); |
| ctx->name = strdup(Name); |
| ctx->srcPath = strdup(SrcPath); |
| ctx->backingFile = strdup(BackingFile); |
| ctx->sectors = atoi(Size); |
| ctx->dstPath = strdup(DstPath); |
| ctx->crypt = strdup(Crypt); |
| return ctx; |
| } |
| |
| void AsecDeinit(void *Handle) |
| { |
| struct asec_context *ctx = (struct asec_context *) Handle; |
| |
| free(ctx->name); |
| free(ctx->srcPath); |
| free(ctx->backingFile); |
| free(ctx->dstPath); |
| free(ctx->crypt); |
| |
| free(ctx); |
| } |
| |
| static int AsecLoadModules() |
| { |
| int i; |
| |
| for (i = 0; MODULES[i] != NULL; i++) { |
| const char *moduleName = MODULES[i]; |
| char moduleFile[255]; |
| int rc = 0; |
| void *module; |
| unsigned int size; |
| |
| sprintf(moduleFile, "%s/%s.ko", MODULE_PATH, moduleName); |
| module = load_file(moduleFile, &size); |
| if (!module) { |
| LOG_ERROR("Failed to load module %s (%d)\n", moduleFile, errno); |
| return -1; |
| } |
| |
| rc = init_module(module, size, ""); |
| free(module); |
| if (rc && errno != EEXIST) { |
| LOG_ERROR("Failed to init module %s (%d)\n", moduleFile, errno); |
| return -errno; |
| } |
| } |
| return 0; |
| } |
| |
| static int AsecUnloadModules() |
| { |
| int i, j, rc; |
| |
| for (i = 0; MODULES[i] != NULL; i++); |
| |
| for (j = (i - 1); j >= 0; j--) { |
| const char *moduleName = MODULES[j]; |
| int maxretry = 10; |
| while(maxretry-- > 0) { |
| rc = delete_module(moduleName, O_NONBLOCK | O_EXCL); |
| if (rc < 0 && errno == EAGAIN) |
| usleep(500000); |
| else |
| break; |
| } |
| if (rc != 0) { |
| LOG_ERROR("Failed to unload module %s\n", moduleName); |
| return -errno; |
| } |
| } |
| return 0; |
| } |
| |
| static int AsecGenerateKey(struct asec_context *ctx) |
| { |
| LOG_ASEC("AsecGenerateKey():\n"); |
| |
| memset((void *) ctx->key, 0x69, sizeof(ctx->key)); |
| return 0; |
| } |
| |
| static int AsecLoadGenerateKey(struct asec_context *ctx) |
| { |
| int fd; |
| int rc = 0; |
| |
| if ((fd = open(KEY_PATH, O_RDWR | O_CREAT, 0600)) < 0) { |
| LOG_ERROR("Error opening / creating keyfile (%d)\n", errno); |
| return -errno; |
| } |
| |
| if (read(fd, ctx->key, sizeof(ctx->key)) != sizeof(ctx->key)) { |
| LOG_ASEC("Generating key\n"); |
| if ((rc = AsecGenerateKey(ctx)) < 0) { |
| LOG_ERROR("Error generating key (%d)\n", rc); |
| goto out; |
| } |
| if (write(fd, ctx->key, sizeof(ctx->key)) != sizeof(ctx->key)) { |
| LOG_ERROR("Error writing keyfile (%d)\n", errno); |
| rc = -1; |
| goto out; |
| } |
| } |
| |
| out: |
| close (fd); |
| return rc; |
| } |
| |
| static int AsecFormatFilesystem(struct asec_context *ctx) |
| { |
| char cmdline[255]; |
| int rc; |
| |
| sprintf(cmdline, |
| "%s -b 4096 -m 1 -j -L \"%s\" /dev/block/dm-%d", |
| MKE2FS_PATH, ctx->name, ctx->dm_num); |
| |
| LOG_ASEC("Formatting filesystem (%s)\n", cmdline); |
| // XXX: PROTECT FROM VIKING KILLER |
| if ((rc = system(cmdline)) < 0) { |
| LOG_ERROR("Error executing format command (%d)\n", errno); |
| return -errno; |
| } |
| |
| rc = WEXITSTATUS(rc); |
| |
| if (!rc) { |
| LOG_ASEC("Format completed\n"); |
| } else { |
| LOG_ASEC("Format failed (%d)\n", rc); |
| } |
| |
| return rc; |
| } |
| |
| static int AsecCheckFilesystem(struct asec_context *ctx) |
| { |
| char cmdline[255]; |
| int rc; |
| |
| sprintf(cmdline, "%s -p /dev/block/dm-%d", E2FSCK_PATH, ctx->dm_num); |
| |
| LOG_ASEC("Checking filesystem (%s)\n", cmdline); |
| // XXX: PROTECT FROM VIKING KILLER |
| if ((rc = system(cmdline)) < 0) { |
| LOG_ERROR("Error executing check command (%d)\n", errno); |
| return -errno; |
| } |
| |
| rc = WEXITSTATUS(rc); |
| |
| if (rc == 0) { |
| LOG_ASEC("ASEC volume '%s' had no errors\n", ctx->name); |
| } else if (rc == 1) { |
| LOG_ASEC("ASEC volume '%s' had corrected errors\n", ctx->name); |
| rc = 0; |
| } else if (rc == 2) { |
| LOG_ERROR("ASEC volume '%s' had corrected errors (system should be rebooted)\n", ctx->name); |
| } else if (rc == 4) { |
| LOG_ERROR("ASEC volume '%s' had uncorrectable errors\n", ctx->name); |
| } else if (rc == 8) { |
| LOG_ERROR("Operational error while checking volume '%s'\n", ctx->name); |
| } else { |
| LOG_ERROR("Unknown e2fsck exit code (%d)\n", rc); |
| } |
| return rc; |
| } |
| |
| static int AsecOpenCreateCache(struct asec_context *ctx) |
| { |
| char filepath[255]; |
| |
| sprintf(filepath, "%s/%s", ctx->srcPath, ctx->backingFile); |
| |
| if ((ctx->cacheFd = open(filepath, O_RDWR)) < 0) { |
| if (errno == ENOENT) { |
| int rc = 0; |
| |
| LOG_ASEC("Creating cache file (%u sectors)\n", ctx->sectors); |
| if ((ctx->cacheFd = creat(filepath, 0600)) < 0) { |
| LOG_ERROR("Error creating cache (%d)\n", errno); |
| return -errno; |
| } |
| if (ftruncate(ctx->cacheFd, ctx->sectors * 512) < 0) { |
| LOG_ERROR("Error truncating cache (%d)\n", errno); |
| close(ctx->cacheFd); |
| unlink(filepath); |
| return -errno; |
| } |
| LOG_ASEC("Cache created (%u sectors) \n", ctx->sectors); |
| close(ctx->cacheFd); // creat() is WRONLY |
| |
| if ((ctx->cacheFd = open(filepath, O_RDWR)) < 0) { |
| LOG_ERROR("Error opening cache file (%d)\n", errno); |
| close(ctx->cacheFd); |
| unlink(filepath); |
| return -errno; |
| } |
| |
| ctx->needs_format = 1; |
| } else |
| return -errno; |
| } else { |
| struct stat stat_buf; |
| |
| if (fstat(ctx->cacheFd, &stat_buf) < 0) { |
| LOG_ERROR("Failed to fstat cache (%d)\n", errno); |
| close(ctx->cacheFd); |
| return -errno; |
| } |
| if (stat_buf.st_size != ctx->sectors * 512) { |
| LOG_ERROR("Cache size %lld != configured size %u\n", |
| stat_buf.st_size, ctx->sectors * 512); |
| } |
| |
| // XXX: Verify volume label matches ctx->name |
| } |
| |
| return 0; |
| } |
| |
| static void AsecCloseCache(struct asec_context *ctx) |
| { |
| close(ctx->cacheFd); |
| } |
| |
| static void *_align(void *ptr, unsigned int a) |
| { |
| register unsigned long agn = --a; |
| |
| return (void *) (((unsigned long) ptr + agn) & ~agn); |
| } |
| |
| static struct dm_ioctl *_dm_ioctl_setup(struct asec_context *ctx, int flags) |
| { |
| void *buffer; |
| void *p; |
| const size_t min_size = 16 * 1024; |
| size_t len = sizeof(struct dm_ioctl); |
| struct dm_ioctl *io; |
| struct dm_target_spec *tgt; |
| int i; |
| char params[1024]; |
| char key[80]; |
| |
| key[0] = '\0'; |
| |
| for (i = 0; i < (int) sizeof(ctx->key); i++) { |
| char tmp[8]; |
| |
| sprintf(tmp, "%02x", ctx->key[i]); |
| strcat(key, tmp); |
| } |
| |
| // XXX: Handle ctx->crypt |
| sprintf(params, "twofish %s 0 /dev/block/loop%d 0", key, ctx->lo_num); |
| |
| if (len < min_size) |
| len = min_size; |
| |
| if (!(buffer = malloc(len))) { |
| LOG_ERROR("Unable to allocate memory\n"); |
| return NULL; |
| } |
| |
| memset(buffer, 0, len); |
| io = buffer; |
| tgt = (struct dm_target_spec *) &buffer[sizeof(struct dm_ioctl)]; |
| |
| io->version[0] = 4; |
| io->version[1] = 0; |
| io->version[2] = 0; |
| |
| io->data_size = len; |
| io->data_start = sizeof(struct dm_ioctl); |
| |
| io->flags = flags; |
| io->dev = 0; |
| |
| io->target_count = 1; |
| io->event_nr = 1; |
| strncpy(io->name, ctx->name, sizeof(io->name)); |
| |
| tgt->status = 0; |
| tgt->sector_start = 0; |
| tgt->length = ctx->sectors; |
| strncpy(tgt->target_type, "crypt", sizeof(tgt->target_type)); |
| |
| p = buffer + sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec); |
| strcpy((char *) p, params); |
| p+= strlen(params) + 1; |
| |
| p = _align(p, 8); |
| tgt->next = p - buffer; |
| |
| return io; |
| } |
| |
| static int FindNextAvailableDm() |
| { |
| int i; |
| |
| for (i = 0; i < 8; i++) { |
| char path[255]; |
| sprintf(path, "/dev/block/dm-%d", i); |
| if ((access(path, F_OK) < 0) && (errno == ENOENT)) |
| return i; |
| } |
| |
| LOG_ERROR("Out of device mapper numbers\n"); |
| return -1; |
| } |
| |
| static int AsecCreateDeviceMapping(struct asec_context *ctx) |
| { |
| struct dm_ioctl *io; |
| int dmFd; |
| int rc = 0; |
| |
| ctx->dm_num = FindNextAvailableDm(); |
| |
| if ((dmFd = open("/dev/device-mapper", O_RDWR)) < 0) { |
| LOG_ERROR("Error opening device mapper (%d)\n", errno); |
| return -errno; |
| } |
| |
| if (!(io = _dm_ioctl_setup(ctx, 0))) { |
| LOG_ERROR("Unable to setup ioctl (out of memory)\n"); |
| close(dmFd); |
| return -ENOMEM; |
| } |
| |
| if ((rc = ioctl(dmFd, DM_DEV_CREATE, io)) < 0) { |
| LOG_ERROR("device-mapper create ioctl failed (%d)\n", errno); |
| rc = -errno; |
| goto out_free; |
| } |
| |
| free(io); |
| |
| if (!(io = _dm_ioctl_setup(ctx, DM_STATUS_TABLE_FLAG))) { |
| LOG_ERROR("Unable to setup ioctl (out of memory)\n"); |
| rc = -ENOMEM; |
| goto out_nofree; |
| } |
| |
| if ((rc = ioctl(dmFd, DM_TABLE_LOAD, io)) < 0) { |
| LOG_ERROR("device-mapper load ioctl failed (%d)\n", errno); |
| rc = -errno; |
| goto out_free; |
| } |
| |
| free(io); |
| |
| if (!(io = _dm_ioctl_setup(ctx, 0))) { |
| LOG_ERROR("Unable to setup ioctl (out of memory)\n"); |
| rc = -ENOMEM; |
| goto out_nofree; |
| } |
| |
| if ((rc = ioctl(dmFd, DM_DEV_SUSPEND, io)) < 0) { |
| LOG_ERROR("device-mapper resume ioctl failed (%d)\n", errno); |
| rc = -errno; |
| goto out_free; |
| } |
| |
| out_free: |
| free (io); |
| out_nofree: |
| close (dmFd); |
| return rc; |
| } |
| |
| static int AsecDestroyDeviceMapping(struct asec_context *ctx) |
| { |
| struct dm_ioctl *io; |
| int dmFd; |
| int rc = 0; |
| |
| if ((dmFd = open("/dev/device-mapper", O_RDWR)) < 0) { |
| LOG_ERROR("Error opening device mapper (%d)\n", errno); |
| return -errno; |
| } |
| |
| if (!(io = _dm_ioctl_setup(ctx, DM_PERSISTENT_DEV_FLAG))) { |
| LOG_ERROR("Unable to setup ioctl (out of memory)\n"); |
| rc = -ENOMEM; |
| goto out_nofree; |
| } |
| |
| if ((rc = ioctl(dmFd, DM_DEV_REMOVE, io)) < 0) { |
| LOG_ERROR("device-mapper remove ioctl failed (%d)\n", errno); |
| rc = -errno; |
| goto out_free; |
| } |
| |
| out_free: |
| free (io); |
| out_nofree: |
| close (dmFd); |
| return rc; |
| } |
| |
| static int AsecMountCache(struct asec_context *ctx) |
| { |
| int flags = MS_NODEV | MS_NOEXEC | MS_NOSUID | MS_NOATIME | MS_NODIRATIME; |
| char devname[255]; |
| |
| if (access(ctx->dstPath, R_OK)) { |
| LOG_ERROR("Destination mount point '%s' unavailable (%d)\n", ctx->dstPath, errno); |
| return -errno; |
| } |
| |
| sprintf(devname, "/dev/block/dm-%d", ctx->dm_num); |
| |
| if (mount(devname, ctx->dstPath, "ext3", flags, NULL)) { |
| LOG_ERROR("ASEC mount failed (%d)\n", errno); |
| return -errno; |
| } |
| |
| return 0; |
| } |
| |
| static int AsecUnmountCache(struct asec_context *ctx) |
| { |
| if (umount(ctx->dstPath)) { |
| if (errno == EBUSY) { |
| LOG_ASEC("ASEC volume '%s' still busy\n", ctx->name); |
| } else { |
| LOG_ERROR("ASEC umount failed (%d)\n", errno); |
| } |
| return -errno; |
| } |
| LOG_ASEC("ASEC volume '%s' unmounted\n", ctx->name); |
| return 0; |
| } |
| |
| static int FindNextAvailableLoop() |
| { |
| int i; |
| |
| for (i = 0; i < MAX_LOOP; i++) { |
| struct loop_info info; |
| char devname[255]; |
| int fd; |
| |
| sprintf(devname, "/dev/block/loop%d", i); |
| |
| if ((fd = open(devname, O_RDONLY)) < 0) { |
| LOG_ERROR("Unable to open %s (%d)\n", devname, errno); |
| return -errno; |
| } |
| |
| if (ioctl(fd, LOOP_GET_STATUS, &info) < 0) { |
| close(fd); |
| |
| if (errno == ENXIO) |
| return i; |
| |
| LOG_ERROR("Unable to get loop status for %s (%d)\n", devname, errno); |
| return -errno; |
| } |
| close(fd); |
| } |
| return -ENXIO; |
| } |
| |
| static int AsecCreateLoop(struct asec_context *ctx) |
| { |
| char devname[255]; |
| int device_fd; |
| int rc = 0; |
| |
| ctx->lo_num = FindNextAvailableLoop(); |
| if (ctx->lo_num < 0) { |
| LOG_ERROR("No loop devices available\n"); |
| return -ENXIO; |
| } |
| |
| sprintf(devname, "/dev/block/loop%d", ctx->lo_num); |
| device_fd = open(devname, O_RDWR); |
| if (device_fd < 0) { |
| LOG_ERROR("failed to open loop device (%d)\n", errno); |
| return -errno; |
| } |
| |
| if (ioctl(device_fd, LOOP_SET_FD, ctx->cacheFd) < 0) { |
| LOG_ERROR("loop_set_fd ioctl failed (%d)\n", errno); |
| rc = -errno; |
| } |
| close(device_fd); |
| return rc; |
| } |
| |
| static int AsecDestroyLoop(struct asec_context *ctx) |
| { |
| char devname[255]; |
| int device_fd; |
| int rc = 0; |
| |
| sprintf(devname, "/dev/block/loop%d", ctx->lo_num); |
| device_fd = open(devname, O_RDONLY); |
| if (device_fd < 0) { |
| LOG_ERROR("Failed to open loop (%d)\n", errno); |
| return -errno; |
| } |
| |
| if (ioctl(device_fd, LOOP_CLR_FD, 0) < 0) { |
| LOG_ERROR("Failed to destroy loop (%d)\n", errno); |
| rc = -errno; |
| } |
| |
| close(device_fd); |
| return rc; |
| } |
| |
| int AsecStart(void *Handle) |
| { |
| struct asec_context *ctx = (struct asec_context *) Handle; |
| char value[PROPERTY_VALUE_MAX]; |
| int rc = 0; |
| |
| if (!ctx) |
| return -EINVAL; |
| |
| if (ctx->started) |
| return -EBUSY; |
| |
| LOG_ASEC("AsecStart(%s):\n", ctx->name); |
| |
| NotifyAsecState(ASEC_BUSY, ctx->dstPath); |
| |
| if ((rc = AsecLoadModules()) < 0) { |
| LOG_ERROR("AsecStart: Failed to load kernel modules\n"); |
| #ifdef MODULE_FAILURE_IS_FATAL |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| return rc; |
| #endif |
| } |
| |
| if ((rc = AsecLoadGenerateKey(ctx))) { |
| LOG_ERROR("AsecStart: Failed to load / generate key\n"); |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| return rc; |
| } |
| |
| if ((rc = AsecOpenCreateCache(ctx)) < 0) { |
| LOG_ERROR("AsecStart: Failed to open / create cache\n"); |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| return rc; |
| } |
| |
| if ((rc = AsecCreateLoop(ctx)) < 0) { |
| LOG_ERROR("AsecStart: Failed to create loop\n"); |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| goto fail_closecache; |
| } |
| |
| if ((rc = AsecCreateDeviceMapping(ctx)) < 0) { |
| LOG_ERROR("AsecStart: Failed to create devmapping (%d)\n", rc); |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| goto fail_destroyloop; |
| } |
| |
| if (ctx->needs_format) { |
| if ((rc = AsecFormatFilesystem(ctx))) { |
| LOG_ERROR("AsecStart: Failed to format cache (%d)\n", rc); |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| goto fail_destroydm; |
| } |
| ctx->needs_format = 0; |
| } else { |
| if ((rc = AsecCheckFilesystem(ctx))) { |
| LOG_ERROR("AsecStart: Failed to check filesystem (%d)\n", rc); |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| goto fail_destroydm; |
| } |
| } |
| |
| if ((rc = AsecMountCache(ctx)) < 0) { |
| LOG_ERROR("AsecStart: Failed to mount cache (%d)\n", rc); |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| goto fail_destroydm; |
| } |
| |
| NotifyAsecState(ASEC_AVAILABLE, ctx->dstPath); |
| ctx->started = true; |
| |
| return rc; |
| |
| fail_destroydm: |
| AsecDestroyDeviceMapping(ctx); |
| fail_destroyloop: |
| AsecDestroyLoop(ctx); |
| fail_closecache: |
| AsecCloseCache(ctx); |
| return rc; |
| } |
| |
| int AsecStop(void *Handle) |
| { |
| struct asec_context *ctx = (struct asec_context *) Handle; |
| int rc = 0; |
| |
| if (!ctx->started) |
| return -EINVAL; |
| |
| LOG_ASEC("AsecStop(%s):\n", ctx->name); |
| |
| NotifyAsecState(ASEC_BUSY, ctx->dstPath); |
| |
| if ((rc = AsecUnmountCache(ctx)) < 0) { |
| LOG_ERROR("AsecStop: Failed to unmount cache (%d)\n", rc); |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| return rc; |
| } |
| |
| if ((rc = AsecDestroyDeviceMapping(ctx)) < 0) { |
| LOG_ERROR("AsecStop: Failed to destroy devmapping (%d)\n", rc); |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| return rc; |
| } |
| |
| if ((rc = AsecDestroyLoop(ctx)) < 0) { |
| LOG_ERROR("AsecStop: Failed to destroy loop device (%d)\n", rc); |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| return rc; |
| } |
| |
| AsecCloseCache(ctx); |
| |
| if ((rc = AsecUnloadModules()) < 0) { |
| if (rc == -EAGAIN) { |
| LOG_ASEC("AsecStop: Kernel modules still in use\n"); |
| } else { |
| LOG_ERROR("AsecStop: Failed to unload kernel modules (%d)\n", rc); |
| #ifdef MODULE_FAILURE_IS_FATAL |
| NotifyAsecState(ASEC_FAILED_INTERR, ctx->dstPath); |
| return rc; |
| #endif |
| } |
| } |
| |
| ctx->started = false; |
| NotifyAsecState(ASEC_DISABLED, ctx->dstPath); |
| return rc; |
| } |