/* * 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. */ #include #include #include #include #include #include #include #include #include #include "vold.h" #include "volmgr.h" #include "blkdev.h" #include "ums.h" #include "format.h" #include "devmapper.h" #include "volmgr_ext3.h" #include "volmgr_vfat.h" #define DEBUG_VOLMGR 0 static volume_t *vol_root = NULL; static boolean safe_mode = true; static struct volmgr_fstable_entry fs_table[] = { { "ext3", ext_identify, ext_check, ext_mount , true }, { "vfat", vfat_identify, vfat_check, vfat_mount , false }, { NULL, NULL, NULL, NULL , false} }; struct _volume_state_event_map { volume_state_t state; char *event; char *property_val; }; static struct _volume_state_event_map volume_state_strings[] = { { volstate_unknown, "volstate_unknown:", "unknown" }, { volstate_nomedia, VOLD_EVT_NOMEDIA, VOLD_ES_PVAL_NOMEDIA }, { volstate_unmounted, VOLD_EVT_UNMOUNTED, VOLD_ES_PVAL_UNMOUNTED }, { volstate_checking, VOLD_EVT_CHECKING, VOLD_ES_PVAL_CHECKING }, { volstate_mounted, VOLD_EVT_MOUNTED, VOLD_ES_PVAL_MOUNTED }, { volstate_mounted_ro, VOLD_EVT_MOUNTED_RO, VOLD_ES_PVAL_MOUNTED_RO }, { volstate_badremoval, VOLD_EVT_BADREMOVAL, VOLD_ES_PVAL_BADREMOVAL }, { volstate_damaged, VOLD_EVT_DAMAGED, VOLD_ES_PVAL_DAMAGED }, { volstate_nofs, VOLD_EVT_NOFS, VOLD_ES_PVAL_NOFS }, { volstate_ums, VOLD_EVT_UMS, VOLD_ES_PVAL_UMS }, { 0, NULL, NULL } }; static int volmgr_readconfig(char *cfg_path); static int volmgr_config_volume(cnode *node); static volume_t *volmgr_lookup_volume_by_mediapath(char *media_path, boolean fuzzy); static volume_t *volmgr_lookup_volume_by_dev(blkdev_t *dev); static int _volmgr_start(volume_t *vol, blkdev_t *dev); static int volmgr_start_fs(struct volmgr_fstable_entry *fs, volume_t *vol, blkdev_t *dev); static void *volmgr_start_fs_thread(void *arg); static void volmgr_start_fs_thread_sighandler(int signo); static void volume_setstate(volume_t *vol, volume_state_t state); static char *conv_volstate_to_eventstr(volume_state_t state); static char *conv_volstate_to_propstr(volume_state_t state); static int volume_send_state(volume_t *vol); static void _cb_volstopped_for_ums_enable(volume_t *v, void *arg); static int _volmgr_enable_ums(volume_t *); static int volmgr_shutdown_volume(volume_t *v, void (* cb) (volume_t *, void *arg), boolean emit_statechange); static int volmgr_stop_volume(volume_t *v, void (*cb) (volume_t *, void *), void *arg, boolean emit_statechange); static void _cb_volume_stopped_for_eject(volume_t *v, void *arg); static void _cb_volume_stopped_for_shutdown(volume_t *v, void *arg); static int _volmgr_consider_disk_and_vol(volume_t *vol, blkdev_t *dev); static void volmgr_uncage_reaper(volume_t *vol, void (* cb) (volume_t *, void *arg), void *arg); static void volmgr_reaper_thread_sighandler(int signo); static void volmgr_add_mediapath_to_volume(volume_t *v, char *media_path); static int volmgr_send_eject_request(volume_t *v); static volume_t *volmgr_lookup_volume_by_mountpoint(char *mount_point, boolean leave_locked); static boolean _mountpoint_mounted(char *mp) { char device[256]; char mount_path[256]; char rest[256]; FILE *fp; char line[1024]; if (!(fp = fopen("/proc/mounts", "r"))) { LOGE("Error opening /proc/mounts (%s)", strerror(errno)); return false; } while(fgets(line, sizeof(line), fp)) { line[strlen(line)-1] = '\0'; sscanf(line, "%255s %255s %255s\n", device, mount_path, rest); if (!strcmp(mount_path, mp)) { fclose(fp); return true; } } fclose(fp); return false; } /* * Public functions */ int volmgr_set_volume_key(char *mount_point, unsigned char *key) { volume_t *v = volmgr_lookup_volume_by_mountpoint(mount_point, true); if (!v) return -ENOENT; if (v->media_type != media_devmapper) { LOGE("Cannot set key on a non devmapper volume"); pthread_mutex_unlock(&v->lock); return -EINVAL; } memcpy(v->dm->key, key, sizeof(v->dm->key)); pthread_mutex_unlock(&v->lock); return 0; } int volmgr_format_volume(char *mount_point) { int rc; volume_t *v; LOG_VOL("volmgr_format_volume(%s):", mount_point); v = volmgr_lookup_volume_by_mountpoint(mount_point, true); if (!v) return -ENOENT; if (v->state == volstate_mounted || v->state == volstate_mounted_ro || v->state == volstate_ums || v->state == volstate_checking) { LOGE("Can't format '%s', currently in state %d", mount_point, v->state); pthread_mutex_unlock(&v->lock); return -EBUSY; } else if (v->state == volstate_nomedia && v->media_type != media_devmapper) { LOGE("Can't format '%s', (no media)", mount_point); pthread_mutex_unlock(&v->lock); return -ENOMEDIUM; } // XXX:Reject if the underlying source media is not present if (v->media_type == media_devmapper) { if ((rc = devmapper_genesis(v->dm)) < 0) { LOGE("devmapper genesis failed for %s (%d)", mount_point, rc); pthread_mutex_unlock(&v->lock); return rc; } } else { if ((rc = initialize_mbr(v->dev->disk)) < 0) { LOGE("MBR init failed for %s (%d)", mount_point, rc); pthread_mutex_unlock(&v->lock); return rc; } } volume_setstate(v, volstate_formatting); pthread_mutex_unlock(&v->lock); return rc; } int volmgr_bootstrap(void) { int rc; if ((rc = volmgr_readconfig("/system/etc/vold.conf")) < 0) { LOGE("Unable to process config"); return rc; } /* * Check to see if any of our volumes is mounted */ volume_t *v = vol_root; while (v) { if (_mountpoint_mounted(v->mount_point)) { LOG_VOL("Volume '%s' already mounted at startup", v->mount_point); v->state = volstate_mounted; } v = v->next; } return 0; } int volmgr_safe_mode(boolean enable) { if (enable == safe_mode) return 0; safe_mode = enable; volume_t *v = vol_root; int rc; while (v) { pthread_mutex_lock(&v->lock); if (v->state == volstate_mounted && v->fs) { rc = v->fs->mount_fn(v->dev, v, safe_mode); if (!rc) { LOG_VOL("Safe mode %s on %s", (enable ? "enabled" : "disabled"), v->mount_point); } else { LOGE("Failed to %s safe-mode on %s (%s)", (enable ? "enable" : "disable" ), v->mount_point, strerror(-rc)); } } pthread_mutex_unlock(&v->lock); v = v->next; } return 0; } int volmgr_send_states(void) { volume_t *vol_scan = vol_root; int rc; while (vol_scan) { pthread_mutex_lock(&vol_scan->lock); if ((rc = volume_send_state(vol_scan)) < 0) { LOGE("Error sending state to framework (%d)", rc); } pthread_mutex_unlock(&vol_scan->lock); vol_scan = vol_scan->next; break; // XXX: } return 0; } /* * Called when a block device is ready to be * evaluated by the volume manager. */ int volmgr_consider_disk(blkdev_t *dev) { volume_t *vol; if (!(vol = volmgr_lookup_volume_by_mediapath(dev->media->devpath, true))) return 0; pthread_mutex_lock(&vol->lock); if (vol->state == volstate_mounted) { LOGE("Volume %s already mounted (did we just crash?)", vol->mount_point); pthread_mutex_unlock(&vol->lock); return 0; } int rc = _volmgr_consider_disk_and_vol(vol, dev); pthread_mutex_unlock(&vol->lock); return rc; } int volmgr_start_volume_by_mountpoint(char *mount_point) { volume_t *v; v = volmgr_lookup_volume_by_mountpoint(mount_point, true); if (!v) return -ENOENT; if (v->media_type == media_devmapper) { if (devmapper_start(v->dm) < 0) { LOGE("volmgr failed to start devmapper volume '%s'", v->mount_point); } } else if (v->media_type == media_mmc) { if (!v->dev) { LOGE("Cannot start volume '%s' (volume is not bound)", mount_point); pthread_mutex_unlock(&v->lock); return -ENOENT; } if (_volmgr_consider_disk_and_vol(v, v->dev->disk) < 0) { LOGE("volmgr failed to start volume '%s'", v->mount_point); } } pthread_mutex_unlock(&v->lock); return 0; } static void _cb_volstopped_for_devmapper_teardown(volume_t *v, void *arg) { devmapper_stop(v->dm); volume_setstate(v, volstate_nomedia); pthread_mutex_unlock(&v->lock); } int volmgr_stop_volume_by_mountpoint(char *mount_point) { int rc; volume_t *v; v = volmgr_lookup_volume_by_mountpoint(mount_point, true); if (!v) return -ENOENT; if (v->state == volstate_mounted) volmgr_send_eject_request(v); if (v->media_type == media_devmapper) rc = volmgr_shutdown_volume(v, _cb_volstopped_for_devmapper_teardown, false); else rc = volmgr_shutdown_volume(v, NULL, true); /* * If shutdown returns -EINPROGRESS, * do *not* release the lock as * it is now owned by the reaper thread */ if (rc != -EINPROGRESS) { if (rc) LOGE("unable to shutdown volume '%s'", v->mount_point); pthread_mutex_unlock(&v->lock); } return 0; } int volmgr_notify_eject(blkdev_t *dev, void (* cb) (blkdev_t *)) { LOG_VOL("Volmgr notified of %d:%d eject", dev->major, dev->minor); volume_t *v; int rc; // XXX: Partitioning support is going to need us to stop *all* // devices in this volume if (!(v = volmgr_lookup_volume_by_dev(dev))) { if (cb) cb(dev); return 0; } pthread_mutex_lock(&v->lock); volume_state_t old_state = v->state; if (v->state == volstate_mounted || v->state == volstate_ums || v->state == volstate_checking) { volume_setstate(v, volstate_badremoval); /* * Stop any devmapper volumes which * are using us as a source * XXX: We may need to enforce stricter * order here */ volume_t *dmvol = vol_root; while (dmvol) { if ((dmvol->media_type == media_devmapper) && (dmvol->dm->src_type == dmsrc_loopback) && (!strncmp(dmvol->dm->type_data.loop.loop_src, v->mount_point, strlen(v->mount_point)))) { pthread_mutex_lock(&dmvol->lock); if (dmvol->state != volstate_nomedia) { rc = volmgr_shutdown_volume(dmvol, _cb_volstopped_for_devmapper_teardown, false); if (rc != -EINPROGRESS) { if (rc) LOGE("unable to shutdown volume '%s'", v->mount_point); pthread_mutex_unlock(&dmvol->lock); } } else pthread_mutex_unlock(&dmvol->lock); } dmvol = dmvol->next; } } else if (v->state == volstate_formatting) { /* * The device is being ejected due to * kernel disk revalidation. */ LOG_VOL("Volmgr ignoring eject of %d:%d (volume formatting)", dev->major, dev->minor); if (cb) cb(dev); pthread_mutex_unlock(&v->lock); return 0; } else volume_setstate(v, volstate_nomedia); if (old_state == volstate_ums) { ums_disable(v->ums_path); pthread_mutex_unlock(&v->lock); } else { int rc = volmgr_stop_volume(v, _cb_volume_stopped_for_eject, cb, false); if (rc != -EINPROGRESS) { if (rc) LOGE("unable to shutdown volume '%s'", v->mount_point); pthread_mutex_unlock(&v->lock); } } return 0; } static void _cb_volume_stopped_for_eject(volume_t *v, void *arg) { void (* eject_cb) (blkdev_t *) = arg; #if DEBUG_VOLMGR LOG_VOL("Volume %s has been stopped for eject", v->mount_point); #endif if (eject_cb) eject_cb(v->dev); v->dev = NULL; // Clear dev because its being ejected } /* * Instructs the volume manager to enable or disable USB mass storage * on any volumes configured to use it. */ int volmgr_enable_ums(boolean enable) { volume_t *v = vol_root; while(v) { if (v->ums_path) { int rc; if (enable) { pthread_mutex_lock(&v->lock); if (v->state == volstate_mounted) volmgr_send_eject_request(v); else if (v->state == volstate_ums) { pthread_mutex_unlock(&v->lock); goto next_vol; } // Stop the volume, and enable UMS in the callback rc = volmgr_shutdown_volume(v, _cb_volstopped_for_ums_enable, false); if (rc != -EINPROGRESS) { if (rc) LOGE("unable to shutdown volume '%s'", v->mount_point); pthread_mutex_unlock(&v->lock); } } else { // Disable UMS pthread_mutex_lock(&v->lock); if (v->state != volstate_ums) { pthread_mutex_unlock(&v->lock); goto next_vol; } if ((rc = ums_disable(v->ums_path)) < 0) { LOGE("unable to disable ums on '%s'", v->mount_point); pthread_mutex_unlock(&v->lock); continue; } LOG_VOL("Kick-starting volume %d:%d after UMS disable", v->dev->disk->major, v->dev->disk->minor); // Start volume if ((rc = _volmgr_consider_disk_and_vol(v, v->dev->disk)) < 0) { LOGE("volmgr failed to consider disk %d:%d", v->dev->disk->major, v->dev->disk->minor); } pthread_mutex_unlock(&v->lock); } } next_vol: v = v->next; } return 0; } /* * Static functions */ static int volmgr_send_eject_request(volume_t *v) { return send_msg_with_data(VOLD_EVT_EJECTING, v->mount_point); } // vol->lock must be held! static int _volmgr_consider_disk_and_vol(volume_t *vol, blkdev_t *dev) { int rc = 0; #if DEBUG_VOLMGR LOG_VOL("volmgr_consider_disk_and_vol(%s, %d:%d):", vol->mount_point, dev->major, dev->minor); #endif if (vol->state == volstate_unknown || vol->state == volstate_mounted || vol->state == volstate_mounted_ro || vol->state == volstate_damaged) { LOGE("Cannot consider volume '%s' because it is in state '%d", vol->mount_point, vol->state); return -EADDRINUSE; } if (vol->state == volstate_formatting) { LOG_VOL("Evaluating dev '%s' for formattable filesystems for '%s'", dev->devpath, vol->mount_point); /* * Since we only support creating 1 partition (right now), * we can just lookup the target by devno */ blkdev_t *part = blkdev_lookup_by_devno(dev->major, 1); if (!part) { part = blkdev_lookup_by_devno(dev->major, 0); if (!part) { LOGE("Unable to find device to format"); return -ENODEV; } } if ((rc = format_partition(part, vol->media_type == media_devmapper ? FORMAT_TYPE_EXT2 : FORMAT_TYPE_FAT32)) < 0) { LOGE("format failed (%d)", rc); return rc; } } LOG_VOL("Evaluating dev '%s' for mountable filesystems for '%s'", dev->devpath, vol->mount_point); if (dev->nr_parts == 0) { rc = _volmgr_start(vol, dev); #if DEBUG_VOLMGR LOG_VOL("_volmgr_start(%s, %d:%d) rc = %d", vol->mount_point, dev->major, dev->minor, rc); #endif } else { /* * Device has multiple partitions * This is where interesting partition policies could be implemented. * For now just try them in sequence until one succeeds */ rc = -ENODEV; int i; for (i = 0; i < dev->nr_parts; i++) { blkdev_t *part = blkdev_lookup_by_devno(dev->major, (i+1)); if (!part) { LOGE("Error - unable to lookup partition for blkdev %d:%d", dev->major, (i+1)); continue; } rc = _volmgr_start(vol, part); #if DEBUG_VOLMGR LOG_VOL("_volmgr_start(%s, %d:%d) rc = %d", vol->mount_point, part->major, part->minor, rc); #endif if (!rc) break; } if (rc == -ENODEV) { // Assert to make sure each partition had a backing blkdev LOGE("Internal consistency error"); return 0; } } if (rc == -ENODATA) { LOGE("Device %d:%d contains no usable filesystems", dev->major, dev->minor); rc = 0; } return rc; } static void volmgr_reaper_thread_sighandler(int signo) { LOGE("Volume reaper thread got signal %d", signo); } static void __reaper_cleanup(void *arg) { volume_t *vol = (volume_t *) arg; if (vol->worker_args.reaper_args.cb) vol->worker_args.reaper_args.cb(vol, vol->worker_args.reaper_args.cb_arg); vol->worker_running = false; // Wake up anyone that was waiting on this thread pthread_mutex_unlock(&vol->worker_sem); // Unlock the volume pthread_mutex_unlock(&vol->lock); } static void *volmgr_reaper_thread(void *arg) { volume_t *vol = (volume_t *) arg; pthread_cleanup_push(__reaper_cleanup, arg); vol->worker_running = true; vol->worker_pid = getpid(); struct sigaction actions; memset(&actions, 0, sizeof(actions)); sigemptyset(&actions.sa_mask); actions.sa_flags = 0; actions.sa_handler = volmgr_reaper_thread_sighandler; sigaction(SIGUSR1, &actions, NULL); LOG_VOL("Reaper here - working on %s", vol->mount_point); boolean send_sig_kill = false; int i, rc; for (i = 0; i < 10; i++) { errno = 0; rc = umount(vol->mount_point); LOG_VOL("volmngr reaper umount(%s) attempt %d (%s)", vol->mount_point, i + 1, strerror(errno)); if (!rc) break; if (rc && (errno == EINVAL || errno == ENOENT)) { rc = 0; break; } sleep(1); if (i >= 4) { KillProcessesWithOpenFiles(vol->mount_point, send_sig_kill, NULL, 0); if (!send_sig_kill) send_sig_kill = true; } } if (!rc) { LOG_VOL("Reaper sucessfully unmounted %s", vol->mount_point); vol->fs = NULL; volume_setstate(vol, volstate_unmounted); } else { LOGE("Unable to unmount!! (%d)", rc); } out: pthread_cleanup_pop(1); pthread_exit(NULL); return NULL; } // vol->lock must be held! static void volmgr_uncage_reaper(volume_t *vol, void (* cb) (volume_t *, void *arg), void *arg) { if (vol->worker_running) { LOGE("Worker thread is currently running.. waiting.."); pthread_mutex_lock(&vol->worker_sem); LOG_VOL("Worker thread now available"); } vol->worker_args.reaper_args.cb = cb; vol->worker_args.reaper_args.cb_arg = arg; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&vol->worker_thread, &attr, volmgr_reaper_thread, vol); } static int volmgr_stop_volume(volume_t *v, void (*cb) (volume_t *, void *), void *arg, boolean emit_statechange) { int i, rc; if (v->state == volstate_mounted || v->state == volstate_badremoval) { // Try to unmount right away (5 retries) for (i = 0; i < 5; i++) { rc = umount(v->mount_point); if (!rc) break; if (rc && (errno == EINVAL || errno == ENOENT)) { rc = 0; break; } LOG_VOL("volmngr quick stop umount(%s) attempt %d (%s)", v->mount_point, i + 1, strerror(errno)); if (i == 0) usleep(1000 * 250); // First failure, sleep for 250 ms else sched_yield(); } if (!rc) { LOG_VOL("volmgr_stop_volume(%s): Volume unmounted sucessfully", v->mount_point); if (emit_statechange) volume_setstate(v, volstate_unmounted); v->fs = NULL; goto out_cb_immed; } /* * Since the volume is still in use, dispatch the stopping to * a thread */ LOG_VOL("Volume %s is busy (%d) - uncaging the reaper", v->mount_point, rc); volmgr_uncage_reaper(v, cb, arg); return -EINPROGRESS; } else if (v->state == volstate_checking) { volume_setstate(v, volstate_unmounted); if (v->worker_running) { LOG_VOL("Cancelling worker thread"); pthread_kill(v->worker_thread, SIGUSR1); } else LOGE("Strange... we were in checking state but worker thread wasn't running.."); goto out_cb_immed; } out_cb_immed: if (cb) cb(v, arg); return 0; } /* * Gracefully stop a volume * v->lock must be held! * if we return -EINPROGRESS, do NOT release the lock as the reaper * is using the volume */ static int volmgr_shutdown_volume(volume_t *v, void (* cb) (volume_t *, void *), boolean emit_statechange) { return volmgr_stop_volume(v, cb, NULL, emit_statechange); } static void _cb_volume_stopped_for_shutdown(volume_t *v, void *arg) { void (* shutdown_cb) (volume_t *) = arg; #if DEBUG_VOLMGR LOG_VOL("Volume %s has been stopped for shutdown", v->mount_point); #endif shutdown_cb(v); } /* * Called when a volume is sucessfully unmounted for UMS enable */ static void _cb_volstopped_for_ums_enable(volume_t *v, void *arg) { int rc; char *devdir_path; #if DEBUG_VOLMGR LOG_VOL("_cb_volstopped_for_ums_enable(%s):", v->mount_point); #endif devdir_path = blkdev_get_devpath(v->dev->disk); if ((rc = ums_enable(devdir_path, v->ums_path)) < 0) { free(devdir_path); LOGE("Error enabling ums (%d)", rc); return; } free(devdir_path); volume_setstate(v, volstate_ums); pthread_mutex_unlock(&v->lock); } static int volmgr_readconfig(char *cfg_path) { cnode *root = config_node("", ""); cnode *node; config_load_file(root, cfg_path); node = root->first_child; while (node) { if (!strncmp(node->name, "volume_", 7)) volmgr_config_volume(node); else LOGE("Skipping unknown configuration node '%s'", node->name); node = node->next; } return 0; } static void volmgr_add_mediapath_to_volume(volume_t *v, char *media_path) { int i; #if DEBUG_VOLMGR LOG_VOL("volmgr_add_mediapath_to_volume(%p, %s):", v, media_path); #endif for (i = 0; i < VOLMGR_MAX_MEDIAPATHS_PER_VOLUME; i++) { if (!v->media_paths[i]) { v->media_paths[i] = strdup(media_path); return; } } LOGE("Unable to add media path '%s' to volume (out of media slots)", media_path); } static int volmgr_config_volume(cnode *node) { volume_t *new; int rc = 0, i; char *dm_src, *dm_src_type, *dm_tgt, *dm_param, *dm_tgtfs; uint32_t dm_size_mb = 0; dm_src = dm_src_type = dm_tgt = dm_param = dm_tgtfs = NULL; #if DEBUG_VOLMGR LOG_VOL("volmgr_configure_volume(%s):", node->name); #endif if (!(new = malloc(sizeof(volume_t)))) return -ENOMEM; memset(new, 0, sizeof(volume_t)); new->state = volstate_nomedia; pthread_mutex_init(&new->lock, NULL); pthread_mutex_init(&new->worker_sem, NULL); cnode *child = node->first_child; while (child) { if (!strcmp(child->name, "media_path")) volmgr_add_mediapath_to_volume(new, child->value); else if (!strcmp(child->name, "emu_media_path")) volmgr_add_mediapath_to_volume(new, child->value); else if (!strcmp(child->name, "media_type")) { if (!strcmp(child->value, "mmc")) new->media_type = media_mmc; else if (!strcmp(child->value, "devmapper")) new->media_type = media_devmapper; else { LOGE("Invalid media type '%s'", child->value); rc = -EINVAL; goto out_free; } } else if (!strcmp(child->name, "mount_point")) new->mount_point = strdup(child->value); else if (!strcmp(child->name, "ums_path")) new->ums_path = strdup(child->value); else if (!strcmp(child->name, "dm_src")) dm_src = strdup(child->value); else if (!strcmp(child->name, "dm_src_type")) dm_src_type = strdup(child->value); else if (!strcmp(child->name, "dm_src_size_mb")) dm_size_mb = atoi(child->value); else if (!strcmp(child->name, "dm_target")) dm_tgt = strdup(child->value); else if (!strcmp(child->name, "dm_target_params")) dm_param = strdup(child->value); else if (!strcmp(child->name, "dm_target_fs")) dm_tgtfs = strdup(child->value); else LOGE("Ignoring unknown config entry '%s'", child->name); child = child->next; } if (new->media_type == media_mmc) { if (!new->media_paths[0] || !new->mount_point || new->media_type == media_unknown) { LOGE("Required configuration parameter missing for mmc volume"); rc = -EINVAL; goto out_free; } } else if (new->media_type == media_devmapper) { if (!dm_src || !dm_src_type || !dm_tgt || !dm_param || !dm_tgtfs || !dm_size_mb) { LOGE("Required configuration parameter missing for devmapper volume"); rc = -EINVAL; goto out_free; } char dm_mediapath[255]; if (!(new->dm = devmapper_init(dm_src, dm_src_type, dm_size_mb, dm_tgt, dm_param, dm_tgtfs, dm_mediapath))) { LOGE("Unable to initialize devmapping"); goto out_free; } LOG_VOL("media path for devmapper volume = '%s'", dm_mediapath); volmgr_add_mediapath_to_volume(new, dm_mediapath); } if (!vol_root) vol_root = new; else { volume_t *scan = vol_root; while (scan->next) scan = scan->next; scan->next = new; } if (dm_src) free(dm_src); if (dm_src_type) free(dm_src_type); if (dm_tgt) free(dm_tgt); if (dm_param) free(dm_param); if (dm_tgtfs) free(dm_tgtfs); return rc; out_free: if (dm_src) free(dm_src); if (dm_src_type) free(dm_src_type); if (dm_tgt) free(dm_tgt); if (dm_param) free(dm_param); if (dm_tgtfs) free(dm_tgtfs); for (i = 0; i < VOLMGR_MAX_MEDIAPATHS_PER_VOLUME; i++) { if (new->media_paths[i]) free(new->media_paths[i]); } if (new->mount_point) free(new->mount_point); if (new->ums_path) free(new->ums_path); return rc; } static volume_t *volmgr_lookup_volume_by_dev(blkdev_t *dev) { volume_t *scan = vol_root; while(scan) { if (scan->dev == dev) return scan; scan = scan->next; } return NULL; } static volume_t *volmgr_lookup_volume_by_mountpoint(char *mount_point, boolean leave_locked) { volume_t *v = vol_root; while(v) { pthread_mutex_lock(&v->lock); if (!strcmp(v->mount_point, mount_point)) { if (!leave_locked) pthread_mutex_unlock(&v->lock); return v; } pthread_mutex_unlock(&v->lock); v = v->next; } return NULL; } static volume_t *volmgr_lookup_volume_by_mediapath(char *media_path, boolean fuzzy) { volume_t *scan = vol_root; int i; while (scan) { for (i = 0; i < VOLMGR_MAX_MEDIAPATHS_PER_VOLUME; i++) { if (!scan->media_paths[i]) continue; if (fuzzy && !strncmp(media_path, scan->media_paths[i], strlen(scan->media_paths[i]))) return scan; else if (!fuzzy && !strcmp(media_path, scan->media_paths[i])) return scan; } scan = scan->next; } return NULL; } /* * Attempt to bring a volume online * Returns: 0 on success, errno on failure, with the following exceptions: * - ENODATA - Unsupported filesystem type / blank * vol->lock MUST be held! */ static int _volmgr_start(volume_t *vol, blkdev_t *dev) { struct volmgr_fstable_entry *fs; int rc = ENODATA; #if DEBUG_VOLMGR LOG_VOL("_volmgr_start(%s, %d:%d):", vol->mount_point, dev->major, dev->minor); #endif if (vol->state == volstate_mounted) { LOGE("Unable to start volume '%s' (already mounted)", vol->mount_point); return -EBUSY; } for (fs = fs_table; fs->name; fs++) { if (!fs->identify_fn(dev)) break; } if (!fs) { LOGE("No supported filesystems on %d:%d", dev->major, dev->minor); volume_setstate(vol, volstate_nofs); return -ENODATA; } return volmgr_start_fs(fs, vol, dev); } // vol->lock MUST be held! static int volmgr_start_fs(struct volmgr_fstable_entry *fs, volume_t *vol, blkdev_t *dev) { /* * Spawn a thread to do the actual checking / mounting in */ if (vol->worker_running) { LOGE("Worker thread is currently running.. waiting.."); pthread_mutex_lock(&vol->worker_sem); LOG_VOL("Worker thread now available"); } vol->dev = dev; vol->worker_args.start_args.fs = fs; vol->worker_args.start_args.dev = dev; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&vol->worker_thread, &attr, volmgr_start_fs_thread, vol); return 0; } static void __start_fs_thread_lock_cleanup(void *arg) { volume_t *vol = (volume_t *) arg; #if DEBUG_VOLMGR LOG_VOL("__start_fs_thread_lock_cleanup(%s):", vol->mount_point); #endif vol->worker_running = false; // Wake up anyone that was waiting on this thread pthread_mutex_unlock(&vol->worker_sem); // Unlock the volume pthread_mutex_unlock(&vol->lock); } static void *volmgr_start_fs_thread(void *arg) { volume_t *vol = (volume_t *) arg; pthread_cleanup_push(__start_fs_thread_lock_cleanup, arg); pthread_mutex_lock(&vol->lock); vol->worker_running = true; vol->worker_pid = getpid(); struct sigaction actions; memset(&actions, 0, sizeof(actions)); sigemptyset(&actions.sa_mask); actions.sa_flags = 0; actions.sa_handler = volmgr_start_fs_thread_sighandler; sigaction(SIGUSR1, &actions, NULL); struct volmgr_fstable_entry *fs = vol->worker_args.start_args.fs; blkdev_t *dev = vol->worker_args.start_args.dev; int rc; #if DEBUG_VOLMGR LOG_VOL("Worker thread pid %d starting %s fs %d:%d on %s", getpid(), fs->name, dev->major, dev->minor, vol->mount_point); #endif if (fs->check_fn) { #if DEBUG_VOLMGR LOG_VOL("Starting %s filesystem check on %d:%d", fs->name, dev->major, dev->minor); #endif volume_setstate(vol, volstate_checking); pthread_mutex_unlock(&vol->lock); rc = fs->check_fn(dev); pthread_mutex_lock(&vol->lock); if (vol->state != volstate_checking) { LOG_VOL("filesystem check aborted"); goto out; } if (rc < 0) { LOGE("%s filesystem check failed on %d:%d (%s)", fs->name, dev->major, dev->minor, strerror(-rc)); if (rc == -ENODATA) { volume_setstate(vol, volstate_nofs); goto out; } goto out_unmountable; } #if DEBUG_VOLMGR LOG_VOL("%s filesystem check of %d:%d OK", fs->name, dev->major, dev->minor); #endif } rc = fs->mount_fn(dev, vol, safe_mode); if (!rc) { LOG_VOL("Sucessfully mounted %s filesystem %d:%d on %s (safe-mode %s)", fs->name, dev->major, dev->minor, vol->mount_point, (safe_mode ? "on" : "off")); vol->fs = fs; volume_setstate(vol, volstate_mounted); goto out; } LOGE("%s filesystem mount of %d:%d failed (%d)", fs->name, dev->major, dev->minor, rc); out_unmountable: volume_setstate(vol, volstate_damaged); out: pthread_cleanup_pop(1); pthread_exit(NULL); return NULL; } static void volmgr_start_fs_thread_sighandler(int signo) { LOGE("Volume startup thread got signal %d", signo); } static void volume_setstate(volume_t *vol, volume_state_t state) { if (state == vol->state) return; #if DEBUG_VOLMGR LOG_VOL("Volume %s state change from %d -> %d", vol->mount_point, vol->state, state); #endif vol->state = state; char *prop_val = conv_volstate_to_propstr(vol->state); if (prop_val) { property_set(PROP_EXTERNAL_STORAGE_STATE, prop_val); volume_send_state(vol); } } static int volume_send_state(volume_t *vol) { char *event = conv_volstate_to_eventstr(vol->state); return send_msg_with_data(event, vol->mount_point); } static char *conv_volstate_to_eventstr(volume_state_t state) { int i; for (i = 0; volume_state_strings[i].event != NULL; i++) { if (volume_state_strings[i].state == state) break; } return volume_state_strings[i].event; } static char *conv_volstate_to_propstr(volume_state_t state) { int i; for (i = 0; volume_state_strings[i].event != NULL; i++) { if (volume_state_strings[i].state == state) break; } return volume_state_strings[i].property_val; }