#include #include #include #include #include #include #define MTAB "/etc/mtab" #define TMPMTAB "/tmp/mtab.detach.tmp" #define DEBUG 0 typedef struct { char dev[256], mp[1024], flags[1024]; } fs; int searchfstab(const char *tabfname, const char *fname, int dev, fs *result); int detachdev(const char *dev); int grepmtab(const char *dev); int main(int argc, char **argv) { fs getdev, filesys, fstab; char *blkdev, *edit; int argind, mtabmodstatus; if( argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help") ) { fprintf(stderr, "Usage: detach ...\n" "Unmounts all file systems on a device and removes it from the system. The\n" "device can be specified as the device file (starting with /dev/) or by the\n" "absolute path of a file on one of its file systems. This program is intended\n" "for use with removable media and will refuse to work on devices which are in\n" "/etc/fstab but are not user mountable.\n"); exit(0); } for( argind= 1; argind< argc; ++argind ) { #if DEBUG printf("arg %s\n", argv[argind]); #endif if( !strncmp(argv[argind], "/dev/", 5) ) { blkdev= argv[argind]; } else { if( !searchfstab("/proc/mounts", argv[argind], 0, &getdev) ) { fprintf(stderr, "Error searching /proc/mounts: %s not found.\n", argv[argind]); continue; } blkdev= getdev.dev; #if DEBUG fprintf(stderr, "Found %s:\n%s \t%s \t%s\n", argv[argind], getdev.dev, getdev.mp, getdev.flags); #endif } for( edit= blkdev + strlen(blkdev) - 1; edit >= blkdev && *edit >= '0' && *edit <= '9'; --edit ) *edit= 0; while( searchfstab("/proc/mounts", blkdev, 1, &filesys) ) { if( searchfstab("/etc/fstab", filesys.mp, 0, &fstab) && !strcmp(filesys.mp, fstab.mp) && (strstr(fstab.flags, "nouser") || !strstr(fstab.flags, "user")) ) { fprintf(stderr, "Error: %s is not user-unmountable.\n", filesys.mp); goto continue_argind; } printf("Unmounting %s from %s...\n", filesys.dev, filesys.mp); errno= 0; if( umount(filesys.mp) || errno ) { perror("Error unmounting"); goto continue_argind; } } for( edit= blkdev + strlen(blkdev) - 1; edit > blkdev && *edit != '/'; --edit ); if( *edit == '/' ) blkdev= edit+1; else blkdev= edit; if( strlen(blkdev) != 3 ) { fprintf(stderr, "Error: device %s not of length 3.\n", blkdev); continue; } printf("Detaching device %s...\n", blkdev); if( detachdev(blkdev) ) { fprintf(stderr, "Error detaching device\n"); continue; } mtabmodstatus= grepmtab(blkdev); if( mtabmodstatus == 2 ) printf("Do not need to remove %s from /etc/mtab.\n", blkdev); else if( mtabmodstatus == 0 ) printf("Could not open /etc/mtab.\n"); else { printf("Removing %s from /etc/mtab...\n", blkdev); if( rename(TMPMTAB, MTAB) ) fprintf(stderr, "Could not update /etc/mtab\n"); } continue_argind: ; } return 0; } /* searchfstab search fstab or similar file for filesystem on which a file name resides. The root file system is never returned (as this is not desirable for this application). If the dev argument is true, the first filesystem on a subdevice of the one given is returned. -> tabfname file name of fstab fname file name to search for dev if non-zero, fname is a block device to search for result storage for fs data; will be used as buffer even if unsuccessful <- 1 success, 0 error or not found */ int searchfstab(const char *tabfname, const char *fname, int dev, fs *result) { FILE *in; struct mntent *entry; int size, len, currlen= 0; in= setmntent(tabfname, "r"); if( !in ) return 0; while( entry = getmntent(in) ) { if( !dev ) if( (len= strlen(entry->mnt_dir)) > currlen && !strncmp(entry->mnt_dir, fname, len) && (!fname[len] || fname[len] == '/') ) { strncpy(result->dev, entry->mnt_fsname, 256); strncpy(result->mp, entry->mnt_dir, 1024); strncpy(result->flags, entry->mnt_opts, 1024); currlen= len; } else; else if( !strncmp(entry->mnt_fsname, fname, strlen(fname)) ) { strncpy(result->dev, entry->mnt_fsname, 256); strncpy(result->mp, entry->mnt_dir, 1024); strncpy(result->flags, entry->mnt_opts, 1024); endmntent(in); return 1; } } endmntent(in); return !!currlen; } /* detachdev detach disk device by writing "1" to /sys/block//device/delete -> dev block device, without /dev/ or subdevice number <- 0 success, 1 write error */ int detachdev(const char *dev) { FILE *w; char delpath[80]; int err; snprintf(delpath, 80, "/sys/block/%s/device/delete", dev); w= fopen(delpath, "w"); if( !w ) return 0; errno= 0; err= fwrite("1\n", 1, 2, w) != 2; err= err || ferror(w) || errno; fclose(w); return err; } /* grepmtab Remove lines referring to a specific block device from /etc/mtab and save the result in a temporary file TMPMTAB -> dev Name of the block device <- 0 error, 1 success, 2 device not found */ int grepmtab(const char *dev) { FILE *in, *copy; struct mntent *entry; int size, len, currlen= 0; in= setmntent(MTAB, "r"); copy= setmntent(TMPMTAB, "w"); if( !in || !copy ) return 0; while( entry = getmntent(in) ) { if( strncmp(entry->mnt_fsname, dev, strlen(dev)) && addmntent(copy, entry) ) { endmntent(in); endmntent(copy); return 0; } } endmntent(in); endmntent(copy); return 2; }