/*
 * 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.
 */

/*
** mountd automount support
*/

#include "mountd.h"

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <pwd.h>
#include <stdlib.h>
#include <poll.h>

#include <sys/mount.h>
#include <sys/stat.h>
#include <linux/loop.h>
#include <sys/inotify.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <linux/netlink.h>

#define DEVPATH    "/dev/block/"
#define DEVPATHLENGTH 11    // strlen(DEVPATH)

// FIXME - only one loop mount is supported at a time
#define LOOP_DEVICE "/dev/block/loop0"

// timeout value for poll() when retries are pending
#define POLL_TIMEOUT    1000

#define MAX_MOUNT_RETRIES   3
#define MAX_UNMOUNT_RETRIES   5

typedef enum {
    // device is unmounted
    kUnmounted,
    
    // attempting to mount device
    kMounting,
    
    // device is unmounted
    kMounted,
    
    // attempting to unmount device
    // so the media can be removed
    kUnmountingForEject,
    
    // attempting to mount device
    // so it can be shared via USB mass storage
    kUnmountingForUms,
} MountState;

typedef struct MountPoint {
    // block device to mount
    const char* device;
    
    // mount point for device
    const char* mountPoint;

    // path to the UMS driver file for specifying the block device path
    const char* driverStorePath;
    
    // true if device can be shared via
    // USB mass storage
    boolean enableUms;
 
    // Array of ASEC handles
    void *asecHandles[ASEC_STORES_MAX];

    // true if the device is being shared via USB mass storage
    boolean umsActive;
    
    // current state of the mount point
    MountState state;
    
    // number of mount or unmount retries so far, 
    // when attempting to mount or unmount the device
    int retryCount; 
 
    // next in sMountPointList linked list
    struct MountPoint* next;   
} MountPoint;

// list of our mount points (does not change after initialization)
static MountPoint* sMountPointList = NULL;
boolean gMassStorageEnabled = false;
boolean gMassStorageConnected = false;

static pthread_t sAutoMountThread = 0;
static pid_t gExcludedPids[2] = {-1, -1};

static const char FSCK_MSDOS_PATH[] = "/system/bin/dosfsck";

// number of mount points that have timeouts pending
static int sRetriesPending = 0;

// for synchronization between sAutoMountThread and the server thread
static pthread_mutex_t sMutex = PTHREAD_MUTEX_INITIALIZER;

// requests the USB mass_storage driver to begin or end sharing a block device
// via USB mass storage.
static void SetBackingStore(MountPoint* mp, boolean enable) 
{
    int fd;

    if (!mp->driverStorePath) {
        LOG_ERROR("no driver_store_path specified in config file for %s", mp->device);
        return;
    }

    LOG_MOUNT("SetBackingStore enable: %s\n", (enable ? "true" : "false"));
    fd = open(mp->driverStorePath, O_WRONLY);
    if (fd < 0)
    {
        LOG_ERROR("could not open driver_store_path %s\n", mp->driverStorePath);
    }
    else
    {
        if (enable)
        {
            write(fd, mp->device, strlen(mp->device));
            mp->umsActive = true;
        }
        else
        {
            char ch = 0;
            write(fd, &ch, 1);
            mp->umsActive = false;
        }
        close(fd);
    }
}

static boolean ReadMassStorageState()
{
    FILE* file = fopen("/sys/class/switch/usb_mass_storage/state", "r");
    if (file)
    {
        char    buffer[20];
        fgets(buffer, sizeof(buffer), file);
        fclose(file);
        return (strncmp(buffer, "online", strlen("online")) == 0);
    }
    else
    {
        LOG_ERROR("could not read initial mass storage state\n");
        return false;
    }
}

static boolean IsLoopMounted(const char* path)
{
    FILE* f;
    int count;
    char device[256];
    char mount_path[256];
    char rest[256];
    int result = 0;
    int path_length = strlen(path);
       
    f = fopen("/proc/mounts", "r");
    if (!f) {
        LOG_ERROR("could not open /proc/mounts\n");
        return -1;
    }

    do {
        count = fscanf(f, "%255s %255s %255s\n", device, mount_path, rest);
        if (count == 3) {
            if (strcmp(LOOP_DEVICE, device) == 0 && strcmp(path, mount_path) == 0)
            {
                result = 1;
                break;
            }
        }
    } while (count == 3);

    fclose(f);
    LOG_MOUNT("IsLoopMounted: %s returning %d\n", path, result);
    return result;
}

static int CheckFilesystem(const char *device)
{
    char cmdline[255];
    int rc;

    // XXX: SAN: Check for FAT signature
    
    int result = access(FSCK_MSDOS_PATH, X_OK);
    if (result != 0) {
        LOG_MOUNT("CheckFilesystem(%s): %s not found (skipping checks)\n", FSCK_MSDOS_PATH, device);
        return 0;
    }
 
    char *args[7];
    args[0] = FSCK_MSDOS_PATH;
    args[1] = "-v";
    args[2] = "-V";
    args[3] = "-w";
    args[4] = "-p";
    args[5] = device;
    args[6] = NULL;

    LOG_MOUNT("Checking filesystem on %s\n", device);
    rc = logwrap(6, args);
  
    // XXX: We need to be able to distinguish between a FS with an error
    // and a block device which does not have a FAT fs at all on it
    if (rc == 0) {
        LOG_MOUNT("Filesystem check completed OK\n");
        return 0;
    } else if (rc == 1) {
        LOG_MOUNT("Filesystem check failed (general failure)\n");
        return -EINVAL;
    } else if (rc == 2) {
        LOG_MOUNT("Filesystem check failed (invalid usage)\n");
        return -EIO;
    } else {
        LOG_MOUNT("Filesystem check failed (unknown exit code %d)\n", rc);
        return -EIO;
    }
}

static int DoMountDevice(const char* device, const char* mountPoint)
{
    LOG_MOUNT("Attempting mount of %s on %s\n", device, mountPoint);

#if CREATE_MOUNT_POINTS
    // make sure mount point exists
    mkdir(mountPoint, 0000);
#endif

    int flags = 0;
    
    if (device && strncmp(device, "/dev/", 5))
    {
        // mount with the loop driver if device does not start with "/dev/"
        int file_fd, device_fd;
        
        // FIXME - only one loop mount supported at a time
        file_fd = open(device, O_RDWR);
        if (file_fd < -1) {
            LOG_ERROR("open backing file %s failed\n", device);
            return 1;
        }
        device_fd = open(LOOP_DEVICE, O_RDWR);
        if (device_fd < -1) {
            LOG_ERROR("open %s failed", LOOP_DEVICE);
            close(file_fd);
            return 1;
        }
        if (ioctl(device_fd, LOOP_SET_FD, file_fd) < 0)
        {
            LOG_ERROR("ioctl LOOP_SET_FD failed\n");
            close(file_fd);
            close(device_fd);
            return 1;
        }

        close(file_fd);
        close(device_fd);
        device = "/dev/block/loop0";
    }

    int result = access(device, R_OK);
    if (result) {
	LOG_ERROR("Unable to access '%s' (%d)\n", device, errno);
   	return -errno;
    }

#if 0
    if ((result = CheckFilesystem(device))) {
        LOG_ERROR("Not mounting filesystem due to check failure (%d)\n", result);
        // XXX:  Notify framework - need a new SDCARD state for the following:
        //       - SD cards which are not present
        //       - SD cards with no partition table
        //       - SD cards with no filesystem
        //       - SD cards with bad filesystem
        return result;
    }
#endif

    // Extra safety measures:
    flags |= MS_NODEV | MS_NOEXEC | MS_NOSUID | MS_DIRSYNC;
    // Also, set fmask = 711 so that files cannot be marked executable,
    // and cannot by opened by uid 1000 (system). Similar, dmask = 700
    // so that directories cannot be accessed by uid 1000.
    result = mount(device, mountPoint, "vfat", flags, 
                       "utf8,uid=1000,gid=1000,fmask=711,dmask=700");
    if (result && errno == EROFS) {
        LOG_ERROR("mount failed EROFS, try again read-only\n");
        flags |= MS_RDONLY;
        result = mount(device, mountPoint, "vfat", flags,
                       "utf8,uid=1000,gid=1000,fmask=711,dmask=700");
    }

    if (result == 0) {
        LOG_MOUNT("Partition %s mounted on %s\n", device, mountPoint);
        NotifyMediaState(mountPoint, MEDIA_MOUNTED, (flags & MS_RDONLY) != 0);

        MountPoint* mp = sMountPointList;
        while (mp) {
            if (!strcmp(mountPoint, mp->mountPoint)) {
                int i;
             
                for (i = 0; i < ASEC_STORES_MAX; i++) {
                    if (mp->asecHandles[i] != NULL) {
                        int a_result;
                        if ((a_result = AsecStart(mp->asecHandles[i])) < 0) {
                            LOG_ERROR("ASEC start failure (%d)\n", a_result);
                        }
                    }
                }
                break;
            }
            mp = mp -> next;
        }
    } else if (errno == EBUSY) {
        LOG_MOUNT("Mount failed (already mounted)\n");
        result = 0;
    } else {
#if CREATE_MOUNT_POINTS
        rmdir(mountPoint);
#endif
        LOG_MOUNT("Unable to mount %s on %s\n", device, mountPoint);
    }

    return result;
}

static int DoUnmountDevice(MountPoint *mp)
{
    boolean loop = IsLoopMounted(mp->mountPoint);
    int i;

    for (i = 0; i < ASEC_STORES_MAX; i++) {
        if (mp->asecHandles[i] && AsecIsStarted(mp->asecHandles[i]))
            AsecStop(mp->asecHandles[i]);
    }

    int result = umount(mp->mountPoint);
    LOG_MOUNT("umount returned %d errno: %d\n", result, errno);

    if (result == 0)
    {
#if CREATE_MOUNT_POINTS
        rmdir(mountPoint);
#endif
        NotifyMediaState(mp->mountPoint, MEDIA_UNMOUNTED, false);
    }

    if (loop)
    {
        // free the loop device
        int loop_fd = open(LOOP_DEVICE, O_RDONLY);
        if (loop_fd < -1) {
            LOG_ERROR("open loop device failed\n");
        }
        if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) {
            LOG_ERROR("ioctl LOOP_CLR_FD failed\n");
        }

        close(loop_fd);
    }

    // ignore EINVAL and ENOENT, since it usually means the device is already unmounted
    if (result && (errno == EINVAL || errno == ENOENT))
        result = 0;

    return result;
}

static int MountPartition(const char* device, const char* mountPoint)
{
    char    buf[100];
    int i;
    
    // attempt to mount subpartitions of the device
    for (i = 1; i < 10; i++)
    {
        int rc;
        snprintf(buf, sizeof(buf), "%sp%d", device, i);
        rc = DoMountDevice(buf, mountPoint);
        LOG_MOUNT("DoMountDevice(%s, %s) = %d\n", buf, mountPoint, rc);
        if (rc == 0)
            return 0;
    }

    return -1;
}

/*****************************************************
 * 
 * AUTO-MOUNTER STATE ENGINE IMPLEMENTATION
 * 
 *****************************************************/

static void SetState(MountPoint* mp, MountState state)
{
    mp->state = state;
}

// Enter a state that requires retries and timeouts.
static void SetRetries(MountPoint* mp, MountState state)
{
    SetState(mp, state);
    mp->retryCount = 0;

    sRetriesPending++;
    // wake up the automounter thread if we are being called 
    // from somewhere else with no retries pending
    if (sRetriesPending == 1 && sAutoMountThread != 0 && 
            pthread_self() != sAutoMountThread)
        pthread_kill(sAutoMountThread, SIGUSR1);
}

// Exit a state that requires retries and timeouts.
static void ClearRetries(MountPoint* mp, MountState state)
{
    SetState(mp, state);
    sRetriesPending--;
}

// attempt to mount the specified mount point.
// set up retry/timeout if it does not succeed at first.
static void RequestMount(MountPoint* mp)
{
    LOG_MOUNT("RequestMount %s\n", mp->mountPoint);

    if (mp->state != kMounted && mp->state != kMounting &&
            access(mp->device, R_OK) == 0) {
        // try raw device first
        if (DoMountDevice(mp->device, mp->mountPoint) == 0 ||
            MountPartition(mp->device, mp->mountPoint) == 0)
        {
            SetState(mp, kMounted);
        }
        else 
        {
            SetState(mp, kMounting);
            mp->retryCount = 0;
            SetRetries(mp, kMounting);
        }
    }
}

// Force the kernel to drop all caches.
static void DropSystemCaches(void)
{
    int fd;

    LOG_MOUNT("Dropping system caches\n");
    fd = open("/proc/sys/vm/drop_caches", O_WRONLY);

    if (fd > 0) {
        char ch = 3;
        int rc;

        rc = write(fd, &ch, 1);
        if (rc <= 0)
            LOG_MOUNT("Error dropping caches (%d)\n", rc);
        close(fd);
    }
}

// attempt to unmount the specified mount point.
// set up retry/timeout if it does not succeed at first.
static void RequestUnmount(MountPoint* mp, MountState retryState)
{
    int result;

    LOG_MOUNT("RequestUnmount %s retryState: %d\n", mp->mountPoint, retryState);
    
    if (mp->state == kMounted)
    {
        SendUnmountRequest(mp->mountPoint);

        // do this in case the user pulls the SD card before we can successfully unmount
        sync();
        DropSystemCaches();

        if (DoUnmountDevice(mp) == 0) 
        {
            SetState(mp, kUnmounted);
            if (retryState == kUnmountingForUms) 
            {
                SetBackingStore(mp, true);
                NotifyMediaState(mp->mountPoint, MEDIA_SHARED, false);
            }
        }
        else 
        {
            LOG_MOUNT("unmount failed, set retry\n");
            SetRetries(mp, retryState);
        }
    } 
    else if (mp->state == kMounting)
    {
        SetState(mp, kUnmounted);
    }
}

// returns true if the mount point should be shared via USB mass storage
static boolean MassStorageEnabledForMountPoint(const MountPoint* mp)
{
    return (gMassStorageEnabled && gMassStorageConnected && mp->enableUms);
}

// handles changes in gMassStorageEnabled and gMassStorageConnected
static void MassStorageStateChanged()
{
    MountPoint* mp = sMountPointList;

    boolean enable = (gMassStorageEnabled && gMassStorageConnected);
    LOG_MOUNT("MassStorageStateChanged enable: %s\n", (enable ? "true" : "false"));
    
    while (mp)
    {
        if (mp->enableUms)
        {
            if (enable)
            {
                if (mp->state == kMounting)
                    SetState(mp, kUnmounted);
                if (mp->state == kUnmounted) 
                {
                    SetBackingStore(mp, true);
                    NotifyMediaState(mp->mountPoint, MEDIA_SHARED, false);
                }
                else
                {
                    LOG_MOUNT("MassStorageStateChanged requesting unmount\n");
                    // need to successfully unmount first
                    RequestUnmount(mp, kUnmountingForUms);
                }
            } else if (mp->umsActive) {
                SetBackingStore(mp, false);
                if (mp->state == kUnmountingForUms)
                {
                    ClearRetries(mp, kMounted);
                    NotifyMediaState(mp->mountPoint, MEDIA_MOUNTED, false);
                }
                else if (mp->state == kUnmounted)
                {
                    NotifyMediaState(mp->mountPoint, MEDIA_UNMOUNTED, false);
                    RequestMount(mp);
                }
            }
        }

        mp = mp->next;
    }
}

// called when USB mass storage connected state changes
static void HandleMassStorageOnline(boolean connected)
{
    if (connected != gMassStorageConnected)
    {
        gMassStorageConnected = connected;
        SendMassStorageConnected(connected);
        
        // we automatically reset to mass storage off after USB is connected
        if (!connected)
            gMassStorageEnabled = false;
    
        MassStorageStateChanged();
    }
}

// called when a new block device has been created
static void HandleMediaInserted(const char* device)
{
    MountPoint* mp = sMountPointList;
    
    LOG_MOUNT("HandleMediaInserted(%s):\n", device);

    while (mp)
    {
        // see if the device matches mount point's block device
        if (mp->state == kUnmounted &&
                strncmp(device, mp->device + DEVPATHLENGTH, strlen(mp->device) - DEVPATHLENGTH) == 0) 
        {
            if (MassStorageEnabledForMountPoint(mp))
            {
                SetBackingStore(mp, true);
                NotifyMediaState(mp->mountPoint, MEDIA_SHARED, false);
            }
            else
                RequestMount(mp);
        }  
        mp = mp->next;
    }
}

// called when a new block device has been deleted
static void HandleMediaRemoved(const char* device)
{    
    MountPoint* mp = sMountPointList;
    while (mp)
    {
        if (strncmp(device, mp->device + DEVPATHLENGTH, strlen(mp->device) - DEVPATHLENGTH) == 0)
        {
            if (mp->enableUms)
                SetBackingStore(mp, false);

             if (mp->state == kMounted) 
            {
                RequestUnmount(mp, kUnmountingForEject);
                NotifyMediaState(mp->mountPoint, MEDIA_BAD_REMOVAL, false);
            }
            
            NotifyMediaState(mp->mountPoint, MEDIA_REMOVED, false);
            break;
        }  
        mp = mp->next;
    }
}

// Handle retrying to mount or unmount devices, 
// and handle timeout condition if we have tried too many times
static void HandleRetries()
{
    MountPoint* mp = sMountPointList;
    
    while (mp)
    {
       if (mp->state == kMounting) 
       {
            if (MountPartition(mp->device, mp->mountPoint) == 0)
            {
                // mount succeeded - clear the retry for this mount point
                ClearRetries(mp, kMounted);
            } 
            else 
            {
                mp->retryCount++;
                if (mp->retryCount == MAX_MOUNT_RETRIES)
                {
                    // we failed to mount the device too many times
                    ClearRetries(mp, kUnmounted);
                    // notify that we failed to mount
                    NotifyMediaState(mp->mountPoint, MEDIA_UNMOUNTABLE, false);
                }
            }
       } 
       else if (mp->state == kUnmountingForEject || mp->state == kUnmountingForUms)
       {
            if (DoUnmountDevice(mp) == 0)
            {
                // unmounting succeeded
                // start mass storage, if state is kUnmountingForUms
                if (mp->state == kUnmountingForUms)
                {
                    SetBackingStore(mp, true);
                     NotifyMediaState(mp->mountPoint, MEDIA_SHARED, false);
                }
                // clear the retry for this mount point
                ClearRetries(mp, kUnmounted);
            } 
            else 
            {
                mp->retryCount++;
                if (mp->retryCount >= MAX_UNMOUNT_RETRIES)
                {
                    // kill any processes that are preventing the device from unmounting
                    // send SIGKILL instead of SIGTERM if the first attempt did not succeed
                    boolean sigkill = (mp->retryCount > MAX_UNMOUNT_RETRIES);
                    
                    int i;

                    for (i = 0; i < ASEC_STORES_MAX; i++) {
                        if (mp->asecHandles[i] && AsecIsStarted(mp->asecHandles[i])) {
                            LOG_MOUNT("Killing processes for ASEC path '%s'\n",
                                      AsecMountPoint(mp->asecHandles[i]));
                            KillProcessesWithOpenFiles(AsecMountPoint(mp->asecHandles[i]),
                                                       sigkill,
                                                       gExcludedPids, sizeof(gExcludedPids) / sizeof(pid_t));

                            // Now that we've killed the processes, try to stop the volume again
                            AsecStop(mp->asecHandles[i]);
                        }
                    }

                    // unmounting the device is failing, so start killing processes
                    KillProcessesWithOpenFiles(mp->mountPoint, sigkill, gExcludedPids, 
                                               sizeof(gExcludedPids) / sizeof(pid_t));

                }
            }
       } 
        
        mp = mp->next;
    }
}

/*****************************************************
 * 
 * AUTO-MOUNTER THREAD
 * 
 *****************************************************/

static void sigusr1_handler(int signo)
{
    // don't need to do anything here
}

// create a socket for listening to inotify events
int CreateINotifySocket()
{
    // initialize inotify
    int fd = inotify_init();

    if (fd < 0) {
        LOG_ERROR("inotify_init failed, %s\n", strerror(errno));
        return -1;
    }

    fcntl(fd, F_SETFL, O_NONBLOCK | fcntl(fd, F_GETFL));

    return fd;
}


// create a socket for listening to uevents
int CreateUEventSocket()
{
    struct sockaddr_nl addr;
    int sz = 64*1024;
    int fd;

    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();
    addr.nl_groups = 0xffffffff;

   fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    if(fd < 0)
    {
        LOG_ERROR("could not create NETLINK_KOBJECT_UEVENT socket\n");
        return -1;
    }

    setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));

    if(bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
        LOG_ERROR("could not bind NETLINK_KOBJECT_UEVENT socket\n");
        close(fd);
        return -1;
    }

    return fd;
}

/*
 * Automounter main event thread.
 * This thread listens for block devices being created and deleted via inotify,
 * and listens for changes in the USB mass storage connected/disconnected via uevents from the 
 * power supply driver.
 * This thread also handles retries and timeouts for requests to mount or unmount a device.
 */
static void* AutoMountThread(void* arg)
{
    int inotify_fd;
    int uevent_fd;
    int id;
    struct sigaction    actions;

    gExcludedPids[1] = getpid();

    memset(&actions, 0, sizeof(actions));
    sigemptyset(&actions.sa_mask);
    actions.sa_flags = 0;
    actions.sa_handler = sigusr1_handler;
    sigaction(SIGUSR1, &actions, NULL);
    
    // initialize inotify
    inotify_fd = CreateINotifySocket();
    // watch for files created and deleted in "/dev"
    inotify_add_watch(inotify_fd, DEVPATH, IN_CREATE|IN_DELETE);

    // initialize uevent watcher
    uevent_fd = CreateUEventSocket();
    if (uevent_fd < 0) 
    {
        LOG_ERROR("CreateUEventSocket failed, %s\n", strerror(errno));
        return NULL;
    }
    
    while (1)
    {
        struct pollfd fds[2];
        int timeout, result;

#define INOTIFY_IDX 0
#define UEVENT_IDX  1
    
        fds[INOTIFY_IDX].fd = inotify_fd;
        fds[INOTIFY_IDX].events = POLLIN;
        fds[INOTIFY_IDX].revents = 0;
        fds[UEVENT_IDX].fd = uevent_fd;
        fds[UEVENT_IDX].events = POLLIN;
        fds[UEVENT_IDX].revents = 0;
        
        // wait for an event or a timeout to occur.
        // poll() can also return in response to a SIGUSR1 signal
        timeout = (sRetriesPending ? POLL_TIMEOUT : -1);
        result = poll(fds, 2, timeout);

        // lock the mutex while we are handling events
        pthread_mutex_lock(&sMutex);

        // handle inotify notifications for block device creation and deletion
        if (fds[INOTIFY_IDX].revents == POLLIN)
        {
            struct inotify_event    event;
            char    buffer[512];
            int length = read(inotify_fd, buffer, sizeof(buffer));
            int offset = 0;
 
            while (length >= (int)sizeof(struct inotify_event))
            {
               struct inotify_event* event = (struct inotify_event *)&buffer[offset];
               
               if (event->mask == IN_CREATE)
               {
                   LOG_MOUNT("/dev/block/%s created\n", event->name);
                   HandleMediaInserted(event->name);
               }
               else if (event->mask == IN_DELETE)
               {
                   LOG_MOUNT("/dev/block/%s deleted\n", event->name);
                   HandleMediaRemoved(event->name);
               }
               
               int size = sizeof(struct inotify_event) + event->len;
               length -= size;
               offset += size;
            }
        }

        // handle uevent notifications for USB state changes
        if (fds[UEVENT_IDX].revents == POLLIN)
        {
            char buffer[64*1024];
            int count;
            
            count = recv(uevent_fd, buffer, sizeof(buffer), 0);
            if (count > 0) {
                char* s = buffer;
                char* end = s + count;
                char* type = NULL;
                char* online = NULL;
                char* switchName = NULL;
                char* switchState = NULL;
                                
                while (s < end) {
                    if (!strncmp("POWER_SUPPLY_TYPE=", s, strlen("POWER_SUPPLY_TYPE=")))
                        type = s + strlen("POWER_SUPPLY_TYPE=");
                    else if (!strncmp("POWER_SUPPLY_ONLINE=", s, strlen("POWER_SUPPLY_ONLINE=")))
                        online = s + strlen("POWER_SUPPLY_ONLINE=");                    
                    else if (!strncmp("SWITCH_NAME=", s, strlen("SWITCH_NAME=")))
                        switchName = s + strlen("SWITCH_NAME=");                    
                    else if (!strncmp("SWITCH_STATE=", s, strlen("SWITCH_STATE=")))
                        switchState = s + strlen("SWITCH_STATE=");                    
                    s += (strlen(s) + 1);
                }

                // we use the usb_mass_storage switch state to tell us when USB is online
                if (switchName && switchState && 
                        !strcmp(switchName, "usb_mass_storage") && !strcmp(switchState, "online"))
                {
                    LOG_MOUNT("USB online\n");
                    HandleMassStorageOnline(true);
                }
                
                // and we use the power supply state to tell us when USB is offline
                // we can't rely on the switch for offline detection because we get false positives
                // when USB is reenumerated by the host.
                if (type && online && !strcmp(type, "USB") && !strcmp(online, "0"))
                {
                    LOG_MOUNT("USB offline\n");
                    HandleMassStorageOnline(false);
                }
            }
        }

       // handle retries
       if (sRetriesPending)
            HandleRetries();

        // done handling events, so unlock the mutex
        pthread_mutex_unlock(&sMutex);
    }

    inotify_rm_watch(inotify_fd, id);
    close(inotify_fd);
    close(uevent_fd);

    return NULL;
}

/*****************************************************
 * 
 * THESE FUNCTIONS ARE CALLED FROM THE SERVER THREAD
 * 
 *****************************************************/

// Called to enable or disable USB mass storage support
void EnableMassStorage(boolean enable)
{
    pthread_mutex_lock(&sMutex);

    LOG_MOUNT("EnableMassStorage %s\n", (enable ? "true" : "false"));
    gMassStorageEnabled = enable;
    MassStorageStateChanged();
    pthread_mutex_unlock(&sMutex);
 }

// Called to request that the specified mount point be mounted
void MountMedia(const char* mountPoint)
{
    MountPoint* mp = sMountPointList;
 
    LOG_MOUNT("MountMedia(%s)\n", mountPoint);
   
    pthread_mutex_lock(&sMutex);
    while (mp)
    {
        if (strcmp(mp->mountPoint, mountPoint) == 0)
        {
            if (mp->state == kUnmountingForEject)
            {
                // handle the case where we try to remount before we actually unmounted
                ClearRetries(mp, kMounted);
            }
            
            // don't attempt to mount if mass storage is active
            if (!MassStorageEnabledForMountPoint(mp))
                RequestMount(mp);
        }
        
        mp = mp->next;
    }
    pthread_mutex_unlock(&sMutex);
 }

// Called to request that the specified mount point be unmounted
void UnmountMedia(const char* mountPoint)
{
    MountPoint* mp = sMountPointList;
    
    pthread_mutex_lock(&sMutex);
    while (mp)
    {
        if (strcmp(mp->mountPoint, mountPoint) == 0)
            RequestUnmount(mp, kUnmountingForEject);
        
        mp = mp->next;
    }
    pthread_mutex_unlock(&sMutex);
}

boolean IsMassStorageEnabled()
{
    return gMassStorageEnabled;
}

boolean IsMassStorageConnected()
{
    return gMassStorageConnected;
}

/***********************************************
 * 
 * THESE FUNCTIONS ARE CALLED ONLY AT STARTUP
 * 
 ***********************************************/
 
void *AddMountPoint(const char* device, const char* mountPoint, const char * driverStorePath, boolean enableUms)
{
    MountPoint* newMountPoint;
    
    LOG_MOUNT("AddMountPoint device: %s, mountPoint: %s driverStorePath: %s\n", device, mountPoint, driverStorePath);
    // add a new MountPoint to the head of our linked list
    newMountPoint = (MountPoint *)malloc(sizeof(MountPoint));
    newMountPoint->device = device;
    newMountPoint->mountPoint = mountPoint;
    newMountPoint->driverStorePath = driverStorePath;
    newMountPoint->enableUms = enableUms;
    newMountPoint->umsActive = false;
    newMountPoint->state = kUnmounted;
    newMountPoint->retryCount = 0;

    // add to linked list
    newMountPoint->next = sMountPointList;
    sMountPointList = newMountPoint;
    return newMountPoint;
}

int AddAsecToMountPoint(void *Mp, const char *name, const char *backing_file, const char *size,
                        const char *mount_point, const char *crypt)
{
    MountPoint *mp = (MountPoint *) Mp;
    int i;

    for (i = 0; i < ASEC_STORES_MAX; i++) {
        if (!mp->asecHandles[i])
            break;   
    }

    if (i == ASEC_STORES_MAX) {
        LOG_ERROR("Maximum # of ASEC stores exceeded\n");
        return -EINVAL;
    }

    if (!(mp->asecHandles[i] = AsecInit(name, mp->mountPoint, backing_file, size, mount_point, crypt)))
        return -1;

    return 0;
}
static void MountDevices()
{
    MountPoint* mp = sMountPointList;
    while (mp)
    {
        RequestMount(mp);
        mp = mp->next;
    }
}

void StartAutoMounter()
{
    gExcludedPids[0] = getpid();

    gMassStorageConnected = ReadMassStorageState();
    LOG_MOUNT(gMassStorageConnected ? "USB online\n" : "USB offline\n");

    MountDevices();
    pthread_create(&sAutoMountThread, NULL, AutoMountThread, NULL);
}
