1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2005-2009 Stanislav Sedov <stas@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 */ 29 #include <sys/cdefs.h> 30 #include <sys/queue.h> 31 #include <sys/stat.h> 32 #include <sys/sysctl.h> 33 #include <sys/user.h> 34 35 #include <assert.h> 36 #include <ctype.h> 37 #include <err.h> 38 #include <fcntl.h> 39 #include <libprocstat.h> 40 #include <limits.h> 41 #include <paths.h> 42 #include <pwd.h> 43 #include <signal.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 #include <sysexits.h> 48 #include <unistd.h> 49 50 #include "functions.h" 51 52 /* 53 * File access mode flags table. 54 */ 55 static const struct { 56 int flag; 57 char ch; 58 } fflags[] = { 59 {PS_FST_FFLAG_WRITE, 'w'}, 60 {PS_FST_FFLAG_APPEND, 'a'}, 61 {PS_FST_FFLAG_DIRECT, 'd'}, 62 {PS_FST_FFLAG_SHLOCK, 's'}, 63 {PS_FST_FFLAG_EXLOCK, 'e'} 64 }; 65 #define NFFLAGS (sizeof(fflags) / sizeof(*fflags)) 66 67 /* 68 * Usage flags translation table. 69 */ 70 static const struct { 71 int flag; 72 char ch; 73 } uflags[] = { 74 {PS_FST_UFLAG_RDIR, 'r'}, 75 {PS_FST_UFLAG_CDIR, 'c'}, 76 {PS_FST_UFLAG_JAIL, 'j'}, 77 {PS_FST_UFLAG_TRACE, 't'}, 78 {PS_FST_UFLAG_TEXT, 'x'}, 79 {PS_FST_UFLAG_MMAP, 'm'}, 80 {PS_FST_UFLAG_CTTY, 'y'} 81 }; 82 #define NUFLAGS (sizeof(uflags) / sizeof(*uflags)) 83 84 struct consumer { 85 pid_t pid; 86 uid_t uid; 87 int fd; 88 int flags; 89 int uflags; 90 STAILQ_ENTRY(consumer) next; 91 }; 92 struct reqfile { 93 dev_t fsid; 94 ino_t fileid; 95 const char *name; 96 STAILQ_HEAD(, consumer) consumers; 97 }; 98 99 /* 100 * Option flags. 101 */ 102 #define UFLAG 0x01 /* -u flag: show users */ 103 #define FFLAG 0x02 /* -f flag: specified files only */ 104 #define CFLAG 0x04 /* -c flag: treat as mpoints */ 105 #define MFLAG 0x10 /* -m flag: mmapped files too */ 106 #define KFLAG 0x20 /* -k flag: send signal (SIGKILL by default) */ 107 108 static int flags = 0; /* Option flags. */ 109 110 static void printflags(struct consumer *consumer); 111 static int str2sig(const char *str); 112 static void usage(void) __dead2; 113 static int addfile(const char *path, struct reqfile *reqfile); 114 static void dofiles(struct procstat *procstat, struct kinfo_proc *kp, 115 struct reqfile *reqfiles, size_t nfiles); 116 117 static void 118 usage(void) 119 { 120 121 fprintf(stderr, 122 "usage: fuser [-cfhkmu] [-M core] [-N system] [-s signal] file ...\n"); 123 exit(EX_USAGE); 124 } 125 126 static void 127 printflags(struct consumer *cons) 128 { 129 unsigned int i; 130 131 assert(cons); 132 for (i = 0; i < NUFLAGS; i++) 133 if ((cons->uflags & uflags[i].flag) != 0) 134 fputc(uflags[i].ch, stderr); 135 for (i = 0; i < NFFLAGS; i++) 136 if ((cons->flags & fflags[i].flag) != 0) 137 fputc(fflags[i].ch, stderr); 138 } 139 140 /* 141 * Add file to the list. 142 */ 143 static int 144 addfile(const char *path, struct reqfile *reqfile) 145 { 146 struct stat sb; 147 148 assert(path); 149 if (stat(path, &sb) != 0) { 150 warn("%s", path); 151 return (1); 152 } 153 reqfile->fileid = sb.st_ino; 154 reqfile->fsid = sb.st_dev; 155 reqfile->name = path; 156 STAILQ_INIT(&reqfile->consumers); 157 return (0); 158 } 159 160 int 161 do_fuser(int argc, char *argv[]) 162 { 163 struct consumer *consumer; 164 struct kinfo_proc *procs; 165 struct procstat *procstat; 166 struct reqfile *reqfiles; 167 char *ep, *nlistf, *memf; 168 int ch, sig; 169 unsigned int i, cnt, nfiles; 170 171 sig = SIGKILL; /* Default to kill. */ 172 nlistf = NULL; 173 memf = NULL; 174 while ((ch = getopt(argc, argv, "M:N:cfhkms:u")) != -1) 175 switch(ch) { 176 case 'f': 177 if ((flags & CFLAG) != 0) 178 usage(); 179 flags |= FFLAG; 180 break; 181 case 'c': 182 if ((flags & FFLAG) != 0) 183 usage(); 184 flags |= CFLAG; 185 break; 186 case 'N': 187 nlistf = optarg; 188 break; 189 case 'M': 190 memf = optarg; 191 break; 192 case 'u': 193 flags |= UFLAG; 194 break; 195 case 'm': 196 flags |= MFLAG; 197 break; 198 case 'k': 199 flags |= KFLAG; 200 break; 201 case 's': 202 if (isdigit(*optarg)) { 203 sig = strtol(optarg, &ep, 10); 204 if (*ep != '\0' || sig < 0 || sig >= sys_nsig) 205 errx(EX_USAGE, "illegal signal number" ": %s", 206 optarg); 207 } else { 208 sig = str2sig(optarg); 209 if (sig < 0) 210 errx(EX_USAGE, "illegal signal name: " 211 "%s", optarg); 212 } 213 break; 214 case 'h': 215 /* PASSTHROUGH */ 216 default: 217 usage(); 218 /* NORETURN */ 219 } 220 argv += optind; 221 argc -= optind; 222 223 assert(argc >= 0); 224 if (argc == 0) 225 usage(); 226 /* NORETURN */ 227 228 /* 229 * Process named files. 230 */ 231 reqfiles = malloc(argc * sizeof(struct reqfile)); 232 if (reqfiles == NULL) 233 err(EX_OSERR, "malloc()"); 234 nfiles = 0; 235 while (argc--) 236 if (!addfile(*(argv++), &reqfiles[nfiles])) 237 nfiles++; 238 if (nfiles == 0) 239 errx(EX_IOERR, "files not accessible"); 240 241 if (memf != NULL) 242 procstat = procstat_open_kvm(nlistf, memf); 243 else 244 procstat = procstat_open_sysctl(); 245 if (procstat == NULL) 246 errx(1, "procstat_open()"); 247 procs = procstat_getprocs(procstat, KERN_PROC_PROC, 0, &cnt); 248 if (procs == NULL) 249 errx(1, "procstat_getprocs()"); 250 251 /* 252 * Walk through process table and look for matching files. 253 */ 254 for (i = 0; i < cnt; i++) 255 if (procs[i].ki_stat != SZOMB) 256 dofiles(procstat, &procs[i], reqfiles, nfiles); 257 258 for (i = 0; i < nfiles; i++) { 259 fprintf(stderr, "%s:", reqfiles[i].name); 260 fflush(stderr); 261 STAILQ_FOREACH(consumer, &reqfiles[i].consumers, next) { 262 if (consumer->flags != 0) { 263 fprintf(stdout, "%6d", consumer->pid); 264 fflush(stdout); 265 printflags(consumer); 266 if ((flags & UFLAG) != 0) 267 fprintf(stderr, "(%s)", 268 user_from_uid(consumer->uid, 0)); 269 if ((flags & KFLAG) != 0) 270 kill(consumer->pid, sig); 271 fflush(stderr); 272 } 273 } 274 (void)fprintf(stderr, "\n"); 275 } 276 procstat_freeprocs(procstat, procs); 277 procstat_close(procstat); 278 free(reqfiles); 279 return (0); 280 } 281 282 static void 283 dofiles(struct procstat *procstat, struct kinfo_proc *kp, 284 struct reqfile *reqfiles, size_t nfiles) 285 { 286 struct vnstat vn; 287 struct consumer *cons; 288 struct filestat *fst; 289 struct filestat_list *head; 290 int error, match; 291 unsigned int i; 292 char errbuf[_POSIX2_LINE_MAX]; 293 294 head = procstat_getfiles(procstat, kp, flags & MFLAG); 295 if (head == NULL) 296 return; 297 STAILQ_FOREACH(fst, head, next) { 298 if (fst->fs_type != PS_FST_TYPE_VNODE) 299 continue; 300 error = procstat_get_vnode_info(procstat, fst, &vn, errbuf); 301 if (error != 0) 302 continue; 303 for (i = 0; i < nfiles; i++) { 304 if (flags & CFLAG && reqfiles[i].fsid == vn.vn_fsid) { 305 break; 306 } 307 else if (reqfiles[i].fsid == vn.vn_fsid && 308 reqfiles[i].fileid == vn.vn_fileid) { 309 break; 310 } 311 else if (!(flags & FFLAG) && 312 (vn.vn_type == PS_FST_VTYPE_VCHR || 313 vn.vn_type == PS_FST_VTYPE_VBLK) && 314 vn.vn_fsid == reqfiles[i].fileid) { 315 break; 316 } 317 } 318 if (i == nfiles) 319 continue; /* No match. */ 320 321 /* 322 * Look for existing entries. 323 */ 324 match = 0; 325 STAILQ_FOREACH(cons, &reqfiles[i].consumers, next) 326 if (cons->pid == kp->ki_pid) { 327 match = 1; 328 break; 329 } 330 if (match == 1) { /* Use old entry. */ 331 cons->flags |= fst->fs_fflags; 332 cons->uflags |= fst->fs_uflags; 333 } else { 334 /* 335 * Create new entry in the consumer chain. 336 */ 337 cons = calloc(1, sizeof(struct consumer)); 338 if (cons == NULL) { 339 warn("malloc()"); 340 continue; 341 } 342 cons->uid = kp->ki_uid; 343 cons->pid = kp->ki_pid; 344 cons->uflags = fst->fs_uflags; 345 cons->flags = fst->fs_fflags; 346 STAILQ_INSERT_TAIL(&reqfiles[i].consumers, cons, next); 347 } 348 } 349 procstat_freefiles(procstat, head); 350 } 351 352 /* 353 * Returns signal number for it's string representation. 354 */ 355 static int 356 str2sig(const char *str) 357 { 358 int i; 359 360 if (!strncasecmp(str, "SIG", 3)) 361 str += 3; 362 for (i = 1; i < sys_nsig; i++) { 363 if (!strcasecmp(sys_signame[i], str)) 364 return (i); 365 } 366 return (-1); 367 } 368