diff --git a/README.md b/README.md index c8229bc..35400e9 100644 --- a/README.md +++ b/README.md @@ -65,3 +65,24 @@ directories to find opened files and seek position, and reports status for the biggest file. It's very light, and compatible with virtually any command. + +Alternatives +------------ + +* [pycp](https://github.com/yannicklm/pycp) + cp and mv with a progress bar (re-implementation in python). + +* rsync --progress or -P + +* kill -s USR1 `pidof dd` + +* SIGINFO (ctrl+t) on OS X and BSDs + +* pv plus tar + +* Patched coreutils like [advanced + copy](http://www.zwicke.org/web/advancedcopy.html) or + [gcp](http://wiki.goffi.org/wiki/Gcp/en). + +* [strace](https://chris-lamb.co.uk/posts/can-you-get-cp-to-give-a-progress-bar-like-wget) + diff --git a/cv.c b/cv.c index c42fa82..c0ab817 100644 --- a/cv.c +++ b/cv.c @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -40,11 +41,16 @@ #include "cv.h" #include "sizes.h" -char *proc_names[] = {"cp", "mv", "dd", "tar", "gzip", "gunzip", "cat", "grep", "fgrep", "egrep", "cut", "sort", NULL}; +char *proc_names[] = {"cp", "mv", "dd", "tar", "gzip", "gunzip", "cat", "grep", "fgrep", "egrep", "cut", "sort"}; char *proc_specifiq = NULL; -signed char flag_quiet = 0; +char *dir_filter = NULL; +signed char flag_quiet = 0, flag_verbose = 0, flag_glob = 0, flag_full = 0, + flag_icase = 0; signed char flag_throughput = 0; double throughput_wait_secs = 1; +long long size_filter = -1; + +int find_fd_for_pid(pid_t pid, int *fd_list, int max_fd, char **dir_names, int num_dirs, long long min_size); signed char is_numeric(char *str) { @@ -56,7 +62,9 @@ while(*str) { return 1; } -int find_pids_by_binary_name(char *bin_name, pidinfo_t *pid_list, int max_pids) +int find_pids_by_binary_name(char **bin_names, int num_names, + char **dir_names, int num_dirs, + pidinfo_t *pid_list, int max_pids, long long min_size) { DIR *proc; struct dirent *direntp; @@ -65,7 +73,8 @@ char fullpath_dir[MAXPATHLEN + 1]; char fullpath_exe[MAXPATHLEN + 1]; char exe[MAXPATHLEN + 1]; ssize_t len; -int pid_count=0; +int pid_count=0, cmdlfd=-1, res=-1, cur_name=0; +char *pnext=NULL, *bin_name=NULL; proc=opendir(PROC_PATH); if(!proc) { @@ -75,40 +84,115 @@ if(!proc) { } while((direntp = readdir(proc)) != NULL) { - snprintf(fullpath_dir, MAXPATHLEN, "%s/%s", PROC_PATH, direntp->d_name); - - if(stat(fullpath_dir, &stat_buf) == -1) { - if (!flag_quiet) - perror("stat (find_pids_by_binary_name)"); - continue; - } + for (cur_name=0; cur_name < num_names; cur_name++) { + bin_name=bin_names[cur_name]; + snprintf(fullpath_dir, MAXPATHLEN, "%s/%s", PROC_PATH, direntp->d_name); - if((S_ISDIR(stat_buf.st_mode) && is_numeric(direntp->d_name))) { - snprintf(fullpath_exe, MAXPATHLEN, "%s/exe", fullpath_dir); - len=readlink(fullpath_exe, exe, MAXPATHLEN); - if(len != -1) - exe[len] = 0; - else { - // Will be mostly "Permission denied" - //~ perror("readlink"); + if(stat(fullpath_dir, &stat_buf) == -1) { + if (!flag_quiet) + fprintf(stderr, "stat (find_pids_by_binary_name): %s: %s\n", strerror(errno), fullpath_dir); continue; } - if(!strcmp(basename(exe), bin_name)) { - pid_list[pid_count].pid=atol(direntp->d_name); - strcpy(pid_list[pid_count].name, bin_name); - pid_count++; - if(pid_count==max_pids) - break; + if((S_ISDIR(stat_buf.st_mode) && is_numeric(direntp->d_name))) { + snprintf(fullpath_exe, MAXPATHLEN, "%s/exe", fullpath_dir); + len=readlink(fullpath_exe, exe, MAXPATHLEN); + if(len != -1) + exe[len] = 0; + else { + if (flag_verbose) + // Will be mostly "Permission denied" + fprintf(stderr, "readlink: %s: %s\n", strerror(errno), fullpath_exe); + continue; + } + + if (flag_icase) + for (pnext=exe; pnextd_name); + strcpy(pid_list[pid_count].name, basename(exe)); + + if ((num_dirs <= 0 && min_size <= 0) || find_fd_for_pid(pid_list[pid_count].pid, NULL, 0, dir_names, num_dirs, min_size) > 0) + pid_count++; + + if(pid_count==max_pids) goto leave; + continue; + } + + // In case the binary name is different then $0 + snprintf(fullpath_exe, MAXPATHLEN, "%s/cmdline", fullpath_dir); + cmdlfd = open(fullpath_exe, O_RDONLY); + if (cmdlfd < 0) { + // Will be mostly "Permission denied" + if (flag_verbose) + fprintf(stderr, "open: %s: %s\n", strerror(errno), fullpath_exe); + } else { + len = read(cmdlfd, exe, MAXPATHLEN); + if (len < 0) { + fprintf(stderr, "read: %s: %s\n", strerror(errno), fullpath_exe); + close(cmdlfd); + } else { + exe[len]=0; + close(cmdlfd); + + if (flag_icase) + for (pnext=exe; pnextd_name); + + if (flag_full) { + // re-null terminate so basename doesn't get + // confused. + pnext = strchr(exe, ' '); + if (pnext) *pnext = 0; + } + strcpy(pid_list[pid_count].name, basename(exe)); + + if ((num_dirs <= 0 && min_size <= 0) || find_fd_for_pid(pid_list[pid_count].pid, NULL, 0, dir_names, num_dirs, min_size) > 0) + pid_count++; + + if(pid_count==max_pids) goto leave; + continue; + } + } + } } } } +leave: closedir(proc); return pid_count; } -int find_fd_for_pid(pid_t pid, int *fd_list, int max_fd) +int find_fd_for_pid(pid_t pid, int *fd_list, int max_fd, char **dir_names, int num_dirs, long long min_size) { DIR *proc; struct dirent *direntp; @@ -116,15 +200,14 @@ char path_dir[MAXPATHLEN + 1]; char fullpath[MAXPATHLEN + 1]; char link_dest[MAXPATHLEN + 1]; struct stat stat_buf; -int count = 0; +int count = 0, i = 0; ssize_t len; snprintf(path_dir, MAXPATHLEN, "%s/%d/fd", PROC_PATH, pid); proc=opendir(path_dir); if(!proc) { - perror("opendir"); - fprintf(stderr,"Can't open %s\n",path_dir); + fprintf(stderr, "opendir: %s: %s\n", strerror(errno), path_dir); return 0; } @@ -132,7 +215,7 @@ while((direntp = readdir(proc)) != NULL) { snprintf(fullpath, MAXPATHLEN, "%s/%s", path_dir, direntp->d_name); if(stat(fullpath, &stat_buf) == -1) { if (!flag_quiet) - perror("stat (find_fd_for_pid)"); + fprintf(stderr, "stat (find_fd_for_pid): %s: %s\n", strerror(errno), fullpath); continue; } @@ -151,11 +234,30 @@ while((direntp = readdir(proc)) != NULL) { if(stat(link_dest, &stat_buf) == -1) continue; - // OK, we've found a potential interesting file. + if (min_size > 0 && stat_buf.st_size < min_size) continue; - fd_list[count++] = atoi(direntp->d_name); - //~ printf("[debug] %s\n",fullpath); - if(count == max_fd) + // OK, we've found a potential interesting file. + if (num_dirs == 0) { + if (fd_list) fd_list[count] = atoi(direntp->d_name); + count++; + } else { + for (i=0; i < num_dirs; i++) { + if (realpath(dir_names[i], fullpath) == NULL) { + if (flag_verbose) + fprintf(stderr, "realpath: %s: %s\n", strerror(errno), dir_names[i]); + continue; + } + if (strstr(link_dest, fullpath) == link_dest) { + if (fd_list) { + fd_list[count] = atoi(direntp->d_name); + if(count == max_fd) break; + } + count++; + } + } + } + //~ printf("[debug] %s\n",link_dest); + if(fd_list && count == max_fd) break; } @@ -181,14 +283,15 @@ len=readlink(fdpath, fd_info->name, MAXPATHLEN); if(len != -1) fd_info->name[len] = 0; else { - //~ perror("readlink"); + if (flag_verbose) + fprintf(stderr, "readlink: %s: %s\n", strerror(errno), fdpath); return 0; } if(stat(fd_info->name, &stat_buf) == -1) { //~ printf("[debug] %i - %s\n",pid,fd_info->name); if (!flag_quiet) - perror("stat (get_fdinfo)"); + fprintf(stderr, "stat (get_fdinfo): %s: %s\n", strerror(errno), fd_info->name); return 0; } @@ -199,13 +302,13 @@ if(S_ISBLK(stat_buf.st_mode)) { if (fd < 0) { if (!flag_quiet) - perror("open (get_fdinfo)"); + fprintf(stderr, "open (get_fdinfo): %s: %s\n", strerror(errno), fd_info->name); return 0; } if (ioctl(fd, BLKGETSIZE64, &fd_info->size) < 0) { if (!flag_quiet) - perror("ioctl (get_fdinfo)"); + fprintf(stderr, "ioctl (get_fdinfo): %s: %s\n", strerror(errno), fd_info->name); return 0; } } else { @@ -220,7 +323,7 @@ gettimeofday(&fd_info->tv, &tz); if(!fp) { if (!flag_quiet) - perror("fopen (get_fdinfo)"); + fprintf(stderr, "fopen (get_fdinfo): %s: %s\n", strerror(errno), fdpath); return 0; } @@ -259,14 +362,20 @@ void parse_options(int argc, char *argv[]) static struct option long_options[] = { {"version", no_argument, 0, 'v'}, {"quiet", no_argument, 0, 'q'}, + {"verbose", no_argument, 0, 'V'}, {"wait", no_argument, 0, 'w'}, {"wait-delay", required_argument, 0, 'W'}, {"help", no_argument, 0, 'h'}, {"command", required_argument, 0, 'c'}, + {"glob", no_argument, 0, 'g'}, + {"full", no_argument, 0, 'f'}, + {"case-insensitively",no_argument,0, 'i'}, + {"directory", required_argument, 0, 'D'}, + {"size", required_argument, 0, 'S'}, {0, 0, 0, 0} }; -static char *options_string = "vqwhc:W:"; +static char *options_string = "vqVwhc:W:gfiD:S:"; int c,i; int option_index = 0; @@ -288,16 +397,23 @@ while(1) { printf("---------------------\n"); printf("Shows running coreutils basic commands and displays stats.\n"); printf("Supported commands: "); - for(i = 0 ; proc_names[i] ; i++) + for(i = 0 ; i < sizeof(proc_names)/sizeof(proc_names[0]); i++) printf("%s ", proc_names[i]); printf("\n"); - printf("Usage: %s [-vqwh] [-W] [-c command]\n",argv[0]); + printf("Usage: %s [-vqVwhgfi] [-W] [-c command] [-D dir] [-S bytes]\n",argv[0]); printf(" -v --version show version\n"); printf(" -q --quiet hides some warning/error messages\n"); + printf(" -V --verbose print non-exceptional errors (eg permission denied)\n"); printf(" -w --wait estimate I/O throughput and ETA (slower display)\n"); printf(" -W --wait-delay secs wait 'secs' seconds for I/O estimation (implies -w, default=%.1f)\n", throughput_wait_secs); printf(" -h --help this message\n"); - printf(" -c --command cmd monitor only this command name (ex: firefox)\n"); + printf(" -c --command cmd monitor only these commands name (ex: firefox,wget)\n"); + printf(" -g --glob use fnmatch() when matching process names instead of strcmp()\n"); + printf(" -f --full match against the entire command line instead of just the command name, implies --glob\n"); + printf(" Note that this is an exact match, use -g and \\*pattern\\* to get a substring match\n"); + printf(" -i --case-insensitive match case insensitively\n"); + printf(" -D --directory dir filter results to processes handling files in dir, comma separated\n"); + printf(" -S --size bytes filter results to processes handling files larger than bytes\n"); exit(EXIT_SUCCESS); break; @@ -306,6 +422,10 @@ while(1) { flag_quiet = 1; break; + case 'V': + flag_verbose = 1; + break; + case 'c': proc_specifiq = strdup(optarg); break; @@ -319,6 +439,31 @@ while(1) { throughput_wait_secs = atof(optarg); break; + case 'g': + flag_glob = 1; + break; + + case 'f': + flag_full = 1; + // Since we are doing an exact match --full isn't useful without + // --glob so imply it for now. Note that this doesn't enable + // substring match, the user still needs to wrap their search + // string in asterisks. + flag_glob = 1; + break; + + case 'i': + flag_icase = 1; + break; + + case 'D': + dir_filter = optarg; + break; + + case 'S': + size_filter = htob(optarg); + break; + case '?': default: exit(EXIT_FAILURE); @@ -342,6 +487,28 @@ if (p->tm_yday) printf("%d:%02d:%02d", p->tm_hour, p->tm_min, p->tm_sec); } +// Split on sep, put pointers int dest. +// Modifies src by replacing sep with '\0', up to len_dest occurrences only. +int split(char *dest[], int len_dest, char *src, char sep) { + int i=0; + char *pnext = src; + if (pnext); + dest[0] = pnext; + i++; + + if (flag_icase) { + for (;*pnext; pnext++) *pnext=tolower(*pnext); + pnext = dest[0]; + } + + while ((pnext = strchr(pnext, sep))) { + *pnext = 0; + if (i==len_dest) break; + dest[i++] = ++pnext; + } + return i; +} + // TODO: deal with --help int main(int argc, char *argv[]) @@ -359,7 +526,10 @@ char ftroughput[64]; struct winsize ws; float perc; result_t results[MAX_RESULTS]; +char *specifiq_batch[MAX_PIDS]; signed char still_there; +char *dir_names[MAX_PIDS]; +int num_dirs=0; parse_options(argc,argv); @@ -368,20 +538,32 @@ ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); pid_count = 0; +if (dir_filter) { + num_dirs = split(dir_names, sizeof(dir_names)/sizeof(dir_names[0]), + dir_filter, ','); +} + if(!proc_specifiq) { - for(i = 0 ; proc_names[i] ; i++) { - pid_count += find_pids_by_binary_name(proc_names[i], - pidinfo_list + pid_count, - MAX_PIDS - pid_count); - if(pid_count >= MAX_PIDS) { - fprintf(stderr, "Found too much procs (max = %d)\n",MAX_PIDS); - break; - } + pid_count = find_pids_by_binary_name(proc_names, + sizeof(proc_names)/sizeof(proc_names[0]), + dir_names, num_dirs, + pidinfo_list, + MAX_PIDS, size_filter); + if(pid_count >= MAX_PIDS) { + fprintf(stderr, "Found too much procs (max = %d)\n",MAX_PIDS); } } else { - pid_count += find_pids_by_binary_name(proc_specifiq, - pidinfo_list + pid_count, - MAX_PIDS - pid_count); + i = split(specifiq_batch, sizeof(specifiq_batch)/sizeof(specifiq_batch[0]), + proc_specifiq, ','); + + pid_count = find_pids_by_binary_name(specifiq_batch, i, + dir_names, num_dirs, + pidinfo_list, + MAX_PIDS, size_filter); + + if(pid_count >= MAX_PIDS) { + fprintf(stderr, "Found too much procs (max = %d)\n",MAX_PIDS); + } } @@ -390,8 +572,14 @@ if(!pid_count) { return 0; fprintf(stderr,"No command currently running: "); - for(i = 0 ; proc_names[i] ; i++) { - fprintf(stderr,"%s, ", proc_names[i]); + if (proc_specifiq) { + for (j=0; j < i; j++) { + fprintf(stderr,"%s, ", specifiq_batch[j]); + } + } else { + for(i = 0 ; i < sizeof(proc_names)/sizeof(proc_names[0]); i++) { + fprintf(stderr,"%s, ", proc_names[i]); + } } fprintf(stderr,"exiting.\n"); return 0; @@ -400,7 +588,7 @@ if(!pid_count) { result_count = 0; for(i = 0 ; i < pid_count ; i++) { - fd_count = find_fd_for_pid(pidinfo_list[i].pid, fdnum_list, MAX_FD_PER_PID); + fd_count = find_fd_for_pid(pidinfo_list[i].pid, fdnum_list, MAX_FD_PER_PID, dir_names, num_dirs, size_filter); max_size = 0; diff --git a/sizes.c b/sizes.c index a6cd696..03f3baf 100644 --- a/sizes.c +++ b/sizes.c @@ -1,3 +1,5 @@ +#include + #include "sizes.h" // Thanks to Jonathan Leffler for this code @@ -28,3 +30,21 @@ for (i = 0 ; i < DIM(sizes) ; i++, multiplier /= 1024) { strcpy(result, "0"); return; } + +long long htob(char *src) { + uint64_t multiplier=1; + int i; + char *p=src; + + while (*p && !isalpha(*p)) p++; + if (!*p) return atoll(src); + + for (i=DIM(sizes)-1; i >= 0; i--) { + if (toupper(*p) == sizes[i][0]) { + return multiplier*atoll(src); + } + multiplier = 1024*multiplier; + } + + return atoll(src); +} diff --git a/sizes.h b/sizes.h index 436a9db..02380cc 100644 --- a/sizes.h +++ b/sizes.h @@ -7,5 +7,6 @@ #include void format_size(uint64_t size, char *result); +long long htob(char *src); #endif