#include #include #include #include #include #include #include #include #include #include static const char *g_ename, /* The name of this executable */ g_popdir[] = "/var/pop", /* Search here for filesystems */ g_umount[] = "/sbin/umount"; /* Path to the umount(1) executable */ static int _checkprivilege(void); static int _exec_umount(const char*); static int _getbasename(char*, size_t, const char*); static int _listfs(int(*)(const char*)); static void _printusage_and_die(const char*); static int _readpath(char*, size_t, int); static int _umount(const char*); static int _umount_list(int, char**); /* usage: pop [option] label [label ...] Options: -a Unmount all unmountable file systems -l (ell) List all unmountable file systems Unmounts the file systems given by each label RETURNS: EXIT_SUCCESS if successful, EXIT_FAILURE otherwise */ int main(int argc, char** argv) { int err, /* Error(s)? */ final_argc; /* Count of arguments in [final_argv] */ char **final_argv; /* List of file system labels */ if( argv[0] != NULL ) g_ename = argv[0]; else g_ename = "pop"; if( argc < 2 ) _printusage_and_die(NULL); if( chdir(g_popdir) == -1 ) { perror("pop: chdir"); (void) fprintf(stderr, "pop: error: failed to change " "to pop directory: %s\n", g_popdir); exit( EXIT_FAILURE ); } #define PRIVILEGED(ACTION) do {\ if( _checkprivilege() ) {\ err = 1;\ goto END;\ }\ \ err = ACTION;\ } while(0) final_argc = argc - 1; final_argv = argv + 1; if( final_argv[0][0] == '-' ) { if( !strcmp("-a", final_argv[0]) ) PRIVILEGED(_listfs(_umount)); else if( !strcmp("-l", final_argv[0]) ) err = _listfs(puts); else if( !strcmp("--", final_argv[0]) ) { --final_argc; ++final_argv; goto UMOUNT_LIST; } else _printusage_and_die("pop: error: unrecognized option"); } else { UMOUNT_LIST: if( final_argc <= 0 ) _printusage_and_die("pop: error: nothing to be done"); PRIVILEGED(_umount_list(final_argc, final_argv)); } #undef PRIVILEGED END: return err ? EXIT_FAILURE : EXIT_SUCCESS; } static int _checkprivilege(void) { int err; /* Returned by setuid(2) */ err = setuid(0); if( err == -1 ) { perror("pop: setuid (0)"); (void) fprintf(stderr, "pop: error: I can't operate without " "super user privileges... sorry\n"); } return err; } static int _exec_umount(const char* label) { int err; /* Status returned by umount(1) */ pid_t umount_pid; /* PID of umount(1) */ assert( label != NULL ); assert( label[0] != '\0' ); err = 1; umount_pid = vfork(); if( umount_pid < 0 ) { perror("pop: fork"); goto END; } if( umount_pid == 0 ) { (void) execlp(g_umount, "umount", label, NULL); perror("pop: execlp (\"umount\")"); (void) fprintf(stderr, "pop: error: failed to execute umount (%s)\n", g_umount); _exit(EXIT_FAILURE); } else { do errno = 0; while( waitpid(umount_pid, &err, WEXITED) == -1 && errno == EINTR ); assert( errno == 0 ); } if( err ) { (void) fprintf(stderr, "pop: unmount failed: %s\n", label); } END: return err; } static int _getbasename(char* basename, size_t basename_len, const char* path) { int err; /* Error(s)? */ const char *path_iter, /* Iterator for [path] */ *slash; /* The final '/' in [path] */ size_t path_len; /* Length of [path], in bytes */ assert( basename != NULL ); assert( basename_len > 0 ); assert( path != NULL ); assert( path[0] != '\0' ); err = 1; slash = strrchr(path, '/'); if( slash ) { path_iter = slash + 1; if( path_iter[0] == '\0' ) { path_iter = slash - 1; for( ; path_iter >= path && path_iter[0] != '/'; path_iter-- ); ++path_iter; } else slash = strchr(path_iter, '\0'); path_len = (size_t) (slash - path_iter); } else { path_iter = path; path_len = strlen(path_iter); } if( path_len == 0 ) { (void) fprintf(stderr, "pop: error: empty label?: %s\n", path); goto END; } if( path_len >= basename_len ) { (void) fprintf(stderr, "pop: error: label is too long: %s\n", path); goto END; } stpncpy(basename, path_iter, path_len)[0] = '\0'; err = 0; END: return err; } static int _listfs(int (*action)(const char*)) { int err; /* Error(s)? */ DIR *popdir; /* Current working directory */ struct dirent *ent; /* Current directory entry */ assert( action != NULL ); err = 1; popdir = opendir("."); if( !popdir ) { perror("pop: opendir (\".\")"); goto END; } errno = 0; while( (ent = readdir(popdir)) != NULL ) { if( ent->d_type == DT_REG ) (void) action(ent->d_name); errno = 0; } if( errno ) { perror("pop: readdir"); goto CLOSEDIR; } err = 0; CLOSEDIR: (void) closedir(popdir); END: return err; } static void _printusage_and_die(const char* msg) { assert( g_ename != NULL ); (void) fprintf(stderr, "usage: %s [option | --] label [label ...]\n\n", g_ename); if( msg != NULL ) (void) fprintf(stderr, "%s\n\n", msg); (void) fprintf(stderr, "Options:\n" " -a unmount all unmountable file systems\n" " -l (ell) list all unmountable file systems\n" "\n"); (void) fprintf(stderr, "Unmounts the file system(s) identified by each label\n"); exit( EXIT_FAILURE ); } static int _readpath(char* path, size_t path_len, int fd) { int err; /* Error(s)? */ ssize_t bytes_read, /* Number of bytes read */ bytes_remain; /* Number of bytes remaining */ char *path_iter; /* Iterator for [path] */ assert( path != NULL ); assert( path_len > 0 ); assert( fd >= 0 ); err = 1; bytes_remain = (ssize_t) path_len; path_iter = path; do { bytes_read = read(fd, path, (size_t) bytes_remain); path_iter = path_iter + bytes_read; bytes_remain = bytes_remain - bytes_read; } while( bytes_read > 0 && bytes_remain > 0 ); if( bytes_read < 0 ) { perror("pop: read"); goto END; } if( bytes_remain <= 0 ) { (void) fprintf(stderr, "pop: error: path name is too long\n"); goto END; } path_iter[0] = '\0'; err = 0; END: return err; } static int _umount(const char* label) { static char basename[128], /* Base name for [label] */ device_path[4096]; /* Path to the device to unmount */ int err, /* Error(s)? */ fd; /* File where the device path is stored */ assert( label != NULL ); err = 1; if( label[0] == '\0' || _getbasename(basename, sizeof(basename), label) ) goto END; (void) fprintf(stderr, "pop: device label is: %s\n", basename); fd = open(basename, O_CLOEXEC | O_EXLOCK | O_NONBLOCK | O_RDONLY); if( fd < 0 ) { perror("pop: open"); (void) fprintf(stderr, "pop: error: failed to open " "file for device: %s\n", label); goto END; } if( _readpath(device_path, sizeof(device_path), fd) ) { (void) fprintf(stderr, "pop: error: failed to get path " "name for device: %s\n", label); goto CLOSE_PATHFILE; } if( _exec_umount(device_path) ) goto CLOSE_PATHFILE; (void) unlink(basename); err = 0; (void) fprintf(stderr, "pop: popped %s (%s)\n", basename, device_path); CLOSE_PATHFILE: (void) close(fd); END: return err; } static int _umount_list(int argc, char** argv) { int err, /* Error(s)? */ i; /* Iterator for [argv] */ assert( argc > 0 ); assert( argv != NULL ); err = 0; for( i = 0; i < argc; i++ ) { if( _umount(argv[i]) ) ++err; } return err; }