|  | 
 | /* | 
 |  * 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 <stdlib.h> | 
 | #include <string.h> | 
 | #include <errno.h> | 
 | #include <dirent.h> | 
 | #include <unistd.h> | 
 | #include <sched.h> | 
 |  | 
 | #include <sys/mount.h> | 
 |  | 
 | #include <cutils/config_utils.h> | 
 | #include <cutils/properties.h> | 
 |  | 
 | #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)) { | 
 |             LOGW("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) { | 
 |                 LOGI("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 || v->state == volstate_nomedia) { | 
 |                     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) { | 
 |         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; | 
 |         if (vol->media_type == media_mmc) | 
 |             part = blkdev_lookup_by_devno(dev->major, ALIGN_MMC_MINOR(dev->minor) + 1); | 
 |         else | 
 |             part = blkdev_lookup_by_devno(dev->major, 1); | 
 |         if (!part) { | 
 |             if (vol->media_type == media_mmc) | 
 |                 part = blkdev_lookup_by_devno(dev->major, ALIGN_MMC_MINOR(dev->minor)); | 
 |             else | 
 |                 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; | 
 |         } | 
 |          | 
 |     } | 
 |  | 
 |     LOGI("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; | 
 |             if (vol->media_type == media_mmc) | 
 |                 part = blkdev_lookup_by_devno(dev->major, ALIGN_MMC_MINOR(dev->minor) + (i+1)); | 
 |             else | 
 |                 part = blkdev_lookup_by_devno(dev->major, (i+1)); | 
 |             if (!part) { | 
 |                 LOGE("Error - unable to lookup partition for blkdev %d:%d", | 
 |                     dev->major, dev->minor); | 
 |                 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 || rc == -EBUSY)  | 
 |                 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); | 
 |  | 
 |     LOGW("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); | 
 |         LOGW("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) { | 
 |         LOGI("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); | 
 |         LOGI("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; | 
 |             } | 
 |  | 
 |             LOGI("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) { | 
 |             LOGI("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 | 
 |          */ | 
 |         LOGW("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); | 
 |         LOGI("Worker thread now available"); | 
 |     } | 
 |  | 
 |     vol->dev = dev;  | 
 |  | 
 |     if (bootstrap) { | 
 |         LOGI("Aborting start of %s (bootstrap = %d)\n", vol->mount_point, | 
 |              bootstrap); | 
 |         vol->state = volstate_unmounted; | 
 |         return -EBUSY; | 
 |     } | 
 |  | 
 |     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) { | 
 |             LOGE("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 | 
 |         LOGI("%s filesystem check of %d:%d OK", fs->name, | 
 |                 dev->major, dev->minor); | 
 | #endif | 
 |     } | 
 |  | 
 |     rc = fs->mount_fn(dev, vol, safe_mode); | 
 |     if (!rc) { | 
 |         LOGI("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; | 
 | } | 
 |  |