| /* |
| * 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); |
| } |