auto usb mount

通过陆陆续续的更正,如今的automount已经基本适应大多数状况。
当用户插入u盘时,调用mdev - (设置/proc/sys/kernel/hotplug),mdev根据/etc/mdev.conf调用相应的过程。
里面还存在一个防止一个程序屡次被启动的方法,open(pathname, O_CREAT|O_EXCL)来实现。
另外注意fork()的子进程会随主进程退出,因此能够放到后台去执行。
相关行以下:
sd[a-z][0-9]* 0:0 0660 */etc/disk_links

disk_links写以下 - 其中check-idong是为了挂载idong disk:
# add for debug
if [ -e "/tmp/automount" ]; then
    echo "disk_links: $ACTION $DEVPATH $MDEV" >> /tmp/automount
fi

if [ "$ACTION" = "add" ]; then
    if echo $DEVPATH | grep "usb"; then
        /opt/bin/automount check-idong &
    fi
elif [ "$ACTION" = "remove" ]; then
    if echo $DEVPATH | grep "usb"; then
        /opt/bin/automount check-idong &
    fi
fi

automount.c的实现以下:

#include <stdio.h>
#include <mntent.h>
#include <stdbool.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <sys/vfs.h>
#include <fcntl.h>
#include <stdlib.h>



//#define USE_IN_PC

#ifdef USE_IN_PC
#define MOUNT_POINT  "/home/qianjiang/tmp/media"
#else
#define MOUNT_POINT  "/tmp/media"
#endif

#define START_DISK_CHAR   'E'
#define END_DISK_CHAR     'Z'
#define MAX_PARTITION_ITEMS    (END_DISK_CHAR - START_DISK_CHAR + 1) //from E: to Z:

typedef struct{
    char devname[120];
    bool bNeedMount;
    char  disk_char;
}stPartitionItem;

static int partition_num;
static stPartitionItem partitions_list[MAX_PARTITION_ITEMS];
typedef enum{
    EVENT_MEDIA_REMOVE = 0,
    EVENT_MEDIA_ADD = 1,
    EVENT_IDONG_REMOVE = 2,
    EVENT_IDONG_ADD = 3,
    EVENT_UNKNOWN = 4
}tEvent;
static tEvent iEvent = EVENT_UNKNOWN;

static FILE * fpDbg = NULL;
#define DebugPrint(x)  if(fpDbg) fprintf x;

static void get_block_backed_filesystems(void);
#define MAX_FS_TYPES            10
#define FS_TYPES_NAME_SIZE      30
static char _astFsTypes[MAX_FS_TYPES][FS_TYPES_NAME_SIZE];
static int  _iFsTypesNum;

static bool CHECK_IDONG_DISK = false;

static int pidof(char * task_name);

static void build_partitions_list(void)
{
    FILE *procpt;
    char line[100], ptname[100], *s;
    int ma, mi, sz;

    procpt = fopen("/proc/partitions", "r");

    partition_num = 0;
    while (fgets(line, sizeof(line), procpt)) {
        if (sscanf(line, " %u %u %u %[^\n ]",
                &ma, &mi, &sz, ptname) != 4)
            continue;
        if(ptname[0] != 's' || ptname[1] != 'd') continue; //we only care about sd*
#ifdef USE_IN_PC
        if(ptname[2] == 'a') continue;
#endif
        #if 0   //qianjiang: do not mount sda or sdb, only mount sda1 or sdb2 etc...
        for (s = ptname; *s; s++)
            continue;
        if (!isdigit(s[-1]))
            continue;
        #endif
        if(partition_num < MAX_PARTITION_ITEMS)
        {
            strcpy(partitions_list[partition_num].devname, ptname);
            partitions_list[partition_num].bNeedMount = true;
            partition_num ++;
        }
    }
    fclose(procpt);
}

//check whether dir is matched $MONT_POINT/[E-Z]
static bool reserved_dir(char * dir)
{
    char * s;

    if(strstr(dir, MOUNT_POINT) != dir) return false;
    s = dir + strlen(MOUNT_POINT);
    while(*s == '/' || *s == '\\') s ++;
    if(*s < START_DISK_CHAR || *s > END_DISK_CHAR) return false;
    s ++;
    if(*s != '\0' && *s != '/' && *s != '\\') return false;
    return true;
}

static char get_disk_char(char * dir)
{
    char * s;

    s = dir + strlen(MOUNT_POINT);
    while(*s == '/' || *s == '\\') s ++;
    return *s;
}

static stPartitionItem * find_device(char * device)
{
    int i;

    for(i = 0; i < partition_num; i ++)
    {
        if(strcmp(partitions_list[i].devname, device) == 0) return &partitions_list[i];
    }
    return NULL;
}

static void scan_mount_points(void)
{
    struct mntent  * mnt;
    FILE           * mntfd;
    struct stat buf;
    pid_t child;
    int status;
    int count;

    mntfd= setmntent("/etc/mtab", "r");
    if(mntfd == NULL) return;

    while(( mnt= getmntent(mntfd)) != NULL)
    {
#ifdef USE_IN_PC
        if(strstr(mnt->mnt_fsname, "/dev/sd") && mnt->mnt_fsname[7] != 'a') //we only care about sd[^a]
#else
        if(strstr(mnt->mnt_fsname, "/dev/sd")) //we only care about sd*
#endif
        {
            if(stat(mnt->mnt_fsname, &buf)) //not exist, we should umount this point
            {
                DebugPrint((fpDbg, "%s is not exist, unmount %s\n", mnt->mnt_fsname, mnt->mnt_dir));

                count = 0;
                while(1) //sometimes, we may umount fail
                {
#if 0
                    if(!(child = fork())){
                        execlp("umount", "umount", mnt->mnt_dir, NULL);
                    }
                    waitpid(child, &status, WUNTRACED);
                    if(WEXITSTATUS(status) == 0)
                    {
                        break;
                    }
#endif
                    if(umount(mnt->mnt_dir) == 0)
                    {
                        break;
                    }

                    count ++;
                    sleep(1);
                    if(count >= 3){
                        umount2(mnt->mnt_dir, MNT_DETACH);
                        break;
                    }
                }

                //check whether we should remove this dir
                if(reserved_dir(mnt->mnt_dir))
                {
                    rmdir(mnt->mnt_dir);
                    iEvent = EVENT_MEDIA_REMOVE;
                }
                else if(strstr(mnt->mnt_dir, "idong"))
                {
                    iEvent = EVENT_IDONG_REMOVE;
                }

            }
            else
            {
                //check whether this device is already mount to $MOUNT_POINT
                if(reserved_dir(mnt->mnt_dir))
                {
                    stPartitionItem * item;
                    //set flag in partitions_list[] we don't need do mount again
                    item = find_device(mnt->mnt_fsname + 5); //strlen("/dev/") is 5
                    if(item)
                    {
                        item->bNeedMount = false;
                        item->disk_char = get_disk_char(mnt->mnt_dir);
                    }
                }
            }
        }
    }                                           
                
    endmntent(mntfd);
}

static void assign_disk_char(void)
{
    int i, j;
    stPartitionItem * item;
    char disk_char = START_DISK_CHAR;
    char path[120];


    //we don't mount sda or sdb if sda1 or sdb4 exist
    for(i = 0; i < partition_num; i ++)
    {
        item = &partitions_list[i];
        if(item->bNeedMount && strlen(item->devname) == 3)
        {
            int j;
            stPartitionItem * new;
            for(j = 0; j < partition_num; j ++)
            {
                new = &partitions_list[j];
                if(strstr(new->devname, item->devname) && strcmp(new->devname, item->devname) > 0)
                    break;
            }
            if(j < partition_num){
                item->bNeedMount = false;
                DebugPrint((fpDbg, "We don't mount the main partition %s\n", item->devname));
            }
        }
    }
    
    for(i = 0; i < partition_num; i ++)
    {
        item = &partitions_list[i];
        if(item->bNeedMount)
        {
            for(j = 0; j < partition_num; j ++)
            {
                if(item->bNeedMount == false && disk_char == item->disk_char)
                {
                    disk_char ++;
                }
            }
            //remove this dir before we use this disk_char
            sprintf(path, "%s/%c", MOUNT_POINT, disk_char);
            rmdir(path);
            item->disk_char = disk_char;
        }
        disk_char ++;
    }
}

static void do_mount(stPartitionItem * item)
{
    pid_t child;
    int status;

    char path[120];
    char devicename[100];

    int i, ret;
    bool try_ntfs_3g = true;

    sprintf(path, "%s/%c", MOUNT_POINT, item->disk_char);
    mkdir( path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH );

    sprintf(devicename, "/dev/%s", item->devname);

    for(i = 0; i < _iFsTypesNum; i ++)
    {
        if(strcmp(_astFsTypes[i], "xfs") == 0) continue;
        if(strcmp(_astFsTypes[i], "squashfs") == 0) continue;
        if(strcmp(_astFsTypes[i], "ntfs") == 0)
        {
            try_ntfs_3g = false;
            ret = mount(devicename, path, _astFsTypes[i], 0, "nls=utf8");
        }
        else if(strncmp(_astFsTypes[i], "ext", 3) == 0) //ext2, ext3 or ext4
        {
            ret = mount(devicename, path, _astFsTypes[i], 0, NULL);
        }
        else //vfat, msdos etc
        {
            ret = mount(devicename, path, _astFsTypes[i], 0, "iocharset=utf8");
        }
        if(ret == 0) //successfully
        {
            iEvent = EVENT_MEDIA_ADD;
            //check whether idong disk
            if(CHECK_IDONG_DISK && strncmp(_astFsTypes[i], "ext", 3) == 0) //idong only exist on ext2, ext3, ext4
            {
                struct stat statbuf;
                char checking_file[100];

                sprintf(checking_file, "%s/iDong-TolTech-Game.bin", path);
                if(stat(checking_file, &statbuf) == 0) //it's idong udisk
                {
                    mount(devicename, "/opt/idong", _astFsTypes[i], 0, "iocharset=utf8");
                    iEvent = EVENT_IDONG_ADD;
                }
            }
            return;
        }
    }

    if(try_ntfs_3g)
    {
        //try userspace filesystem: ntfs-3g
        if(!(child = fork())){
            execlp("ntfs-3g", "ntfs-3g", "-o", "force", devicename, path, NULL);
        }
        waitpid(child, &status, WUNTRACED);
        if(WEXITSTATUS(status) == 0) //mount successfully
        {
            iEvent = EVENT_MEDIA_ADD;
            return;
        }
    }

    rmdir(path);
}


static void mount_all(void)
{
    int i;
    stPartitionItem * item;
    for(i = 0; i < partition_num; i ++)
    {
        item = &partitions_list[i];

        if(item->bNeedMount)
        {
             DebugPrint((fpDbg, "do_mount /dev/%s %s/%c\n", item->devname, MOUNT_POINT, item->disk_char));
             do_mount(item);
        }
    }
}

int main_proc(int argc, char * argv[])
{
    char * action, * mdev;
    char pathname[100];
    int count;

    action = getenv("ACTION");
    mdev = getenv("MDEV");

    if(access("/tmp/automount", W_OK) == 0) fpDbg = fopen("/tmp/automount", "w");
//    fpDbg = stdout;
    DebugPrint((fpDbg, "Checking USB DISK ...%s %s\n", action, mdev));

    if(action != NULL && mdev != NULL)
    {
        sprintf(pathname, "/dev/%s", mdev);
        count = 0;
        if(strcmp(action, "add") == 0)
        {
            if(strlen(mdev) == 3) sleep(1); //sda or sdb, we sleep a while to wait for sub partition exist
            while(access(pathname, F_OK) != 0)
            {
                DebugPrint((fpDbg, "device is not added to /dev yet!!!\n"));
                sleep(1);
                if(++ count  > 2) break;
            }
        }
        else if(strcmp(action, "remove") == 0)
        {
            while(access(pathname, F_OK) == 0)
            {
                DebugPrint((fpDbg, "device is not removed from /dev yet\n"));
                sleep(1);
                if(++ count  > 2) break;
            }
        }
    }

    mkdir( MOUNT_POINT, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ); //first create root directory if not exit

    if(argc > 1 && strcmp(argv[1], "check-idong") == 0)
    {
        CHECK_IDONG_DISK = true;
    }

    get_block_backed_filesystems();
    build_partitions_list();
    scan_mount_points();
    assign_disk_char();
    mount_all();

    if(iEvent != EVENT_UNKNOWN)
    {
        int pid;
        union sigval val;

        pid = pidof("SystemMonitor");
        if(pid > 0)
        {
            val.sival_int = iEvent;
            sigqueue(pid, SIGRTMIN, val);
            DebugPrint((fpDbg, "Send message to SystemMonitor\n"));
        }
    }
    
    DebugPrint((fpDbg, "Done\n"));
    if(fpDbg) fclose(fpDbg);
    return -1;
}

int main(int argc, char * argv[])
{
//    pid_t child;

//    if(!(child = fork())) {
        //we need avoid multi-call of automount
        int fd;
        int count = 0;
        while((fd = open("/tmp/automount.lock", O_CREAT | O_EXCL)) < 0){
            //file exist, wait for its removal
            sleep(1);
            if(count ++ > 20) break;
        }
        if(fd >= 0) close(fd);

        main_proc(argc, argv);

        unlink("/tmp/automount.lock");
//    }
    return 0;
}


//============================== Get from busybox mount.c =============================================
//
char* skip_whitespace(const char *s)
{
    /* In POSIX/C locale (the only locale we care about: do we REALLY want
     * to allow Unicode whitespace in, say, .conf files? nuts!)
     * isspace is only these chars: "\t\n\v\f\r" and space.
     * "\t\n\v\f\r" happen to have ASCII codes 9,10,11,12,13.
     * Use that.
     */
    while (*s == ' ' || (unsigned char)(*s - 9) <= (13 - 9))
        s++;

    return (char *) s;
}

// Return a list of all block device backed filesystems
static void get_block_backed_filesystems(void)
{
    static const char filesystems[2][sizeof("/proc/filesystems")] = {
        "/etc/filesystems",
        "/proc/filesystems",
    };
    char *fs, buf[100], *tmp;
    int i;
    FILE *f;

    _iFsTypesNum = 0;
    for (i = 0; i < 2; i++) {
        f = fopen(filesystems[i], "r");
        if (!f) continue;

        while ((fgets(buf, 100, f)) != NULL) {
            if (strncmp(buf, "nodev", 5) == 0 && isspace(buf[5]))
                continue;
            fs = skip_whitespace(buf);
            if (*fs == '#' || *fs == '*' || !*fs)
                continue;

            //skip newline or space
            tmp = fs;
            while(*tmp)
            {
                if(*tmp == ' ' || (unsigned char)(*tmp - 9) <= (13 - 9))
                {
                    *tmp = 0;
                    break;
                }
                tmp ++;
            }
            //keep filesystem
            if(*fs)
            {
                strncpy(_astFsTypes[_iFsTypesNum], fs, FS_TYPES_NAME_SIZE);
                _astFsTypes[_iFsTypesNum ++ ][FS_TYPES_NAME_SIZE - 1] = 0;
                if(_iFsTypesNum >= MAX_FS_TYPES) break;
            }
        }
        fclose(f);
    }

    return ;
}


//
/* > 0 means successfully find the pid, else fail*/
static int pidof(char * task_name)
{
    DIR * dir;
    struct dirent * ent;
    int pid;
    char filename[100];
    struct statfs buf;
    int fd;
    char cmdline[256];


    if(!(dir = opendir("/proc"))) {
        return -1;
    }
    
    while((ent = readdir(dir))){
        pid = atoi(ent->d_name);
        sprintf(filename, "/proc/%d", pid);
        if((pid > 0) && (statfs(filename, &buf) == 0))
        {
            strcat(filename, "/cmdline");
            fd = open(filename, O_RDONLY);
            if(fd < 0) continue;
            memset(cmdline, 0, 256);
            read(fd, cmdline, 256);

            close(fd);
            if(strstr(cmdline, task_name))
                return pid;
        }
    }

    closedir(dir);
    return -1;
}
 node