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 <sysexits.h> 47 #include <unistd.h> 48 49 #include "functions.h" 50 51 /* 52 * File access mode flags table. 53 */ 54 static const struct { 55 int flag; 56 char ch; 57 } fflags[] = { 58 {PS_FST_FFLAG_WRITE, 'w'}, 59 {PS_FST_FFLAG_APPEND, 'a'}, 60 {PS_FST_FFLAG_DIRECT, 'd'}, 61 {PS_FST_FFLAG_SHLOCK, 's'}, 62 {PS_FST_FFLAG_EXLOCK, 'e'} 63 }; 64 #define NFFLAGS (sizeof(fflags) / sizeof(*fflags)) 65 66 /* 67 * Usage flags translation table. 68 */ 69 static const struct { 70 int flag; 71 char ch; 72 } uflags[] = { 73 {PS_FST_UFLAG_RDIR, 'r'}, 74 {PS_FST_UFLAG_CDIR, 'c'}, 75 {PS_FST_UFLAG_JAIL, 'j'}, 76 {PS_FST_UFLAG_TRACE, 't'}, 77 {PS_FST_UFLAG_TEXT, 'x'}, 78 {PS_FST_UFLAG_MMAP, 'm'}, 79 {PS_FST_UFLAG_CTTY, 'y'} 80 }; 81 #define NUFLAGS (sizeof(uflags) / sizeof(*uflags)) 82 83 struct consumer { 84 pid_t pid; 85 uid_t uid; 86 int fd; 87 int flags; 88 int uflags; 89 STAILQ_ENTRY(consumer) next; 90 }; 91 struct reqfile { 92 dev_t fsid; 93 ino_t fileid; 94 const char *name; 95 STAILQ_HEAD(, consumer) consumers; 96 }; 97 98 /* 99 * Option flags. 100 */ 101 #define UFLAG 0x01 /* -u flag: show users */ 102 #define FFLAG 0x02 /* -f flag: specified files only */ 103 #define CFLAG 0x04 /* -c flag: treat as mpoints */ 104 #define MFLAG 0x10 /* -m flag: mmapped files too */ 105 #define KFLAG 0x20 /* -k flag: send signal (SIGKILL by default) */ 106 107 static int flags = 0; /* Option flags. */ 108 109 static void printflags(struct consumer *consumer); 110 static void usage(void) __dead2; 111 static int addfile(const char *path, struct reqfile *reqfile); 112 static void dofiles(struct procstat *procstat, struct kinfo_proc *kp, 113 struct reqfile *reqfiles, size_t nfiles); 114 115 static void 116 usage(void) 117 { 118 119 fprintf(stderr, 120 "usage: fuser [-cfhkmu] [-M core] [-N system] [-s signal] file ...\n"); 121 exit(EX_USAGE); 122 } 123 124 static void 125 printflags(struct consumer *cons) 126 { 127 unsigned int i; 128 129 assert(cons); 130 for (i = 0; i < NUFLAGS; i++) 131 if ((cons->uflags & uflags[i].flag) != 0) 132 fputc(uflags[i].ch, stderr); 133 for (i = 0; i < NFFLAGS; i++) 134 if ((cons->flags & fflags[i].flag) != 0) 135 fputc(fflags[i].ch, stderr); 136 } 137 138 /* 139 * Add file to the list. 140 */ 141 static int 142 addfile(const char *path, struct reqfile *reqfile) 143 { 144 struct stat sb; 145 146 assert(path); 147 if (stat(path, &sb) != 0) { 148 warn("%s", path); 149 return (1); 150 } 151 reqfile->fileid = sb.st_ino; 152 reqfile->fsid = sb.st_dev; 153 reqfile->name = path; 154 STAILQ_INIT(&reqfile->consumers); 155 return (0); 156 } 157 158 int 159 do_fuser(int argc, char *argv[]) 160 { 161 struct consumer *consumer; 162 struct kinfo_proc *procs; 163 struct procstat *procstat; 164 struct reqfile *reqfiles; 165 char *nlistf, *memf; 166 int ch, sig; 167 unsigned int i, cnt, nfiles; 168 169 sig = SIGKILL; /* Default to kill. */ 170 nlistf = NULL; 171 memf = NULL; 172 while ((ch = getopt(argc, argv, "M:N:cfhkms:u")) != -1) 173 switch(ch) { 174 case 'f': 175 if ((flags & CFLAG) != 0) 176 usage(); 177 flags |= FFLAG; 178 break; 179 case 'c': 180 if ((flags & FFLAG) != 0) 181 usage(); 182 flags |= CFLAG; 183 break; 184 case 'N': 185 nlistf = optarg; 186 break; 187 case 'M': 188 memf = optarg; 189 break; 190 case 'u': 191 flags |= UFLAG; 192 break; 193 case 'm': 194 flags |= MFLAG; 195 break; 196 case 'k': 197 flags |= KFLAG; 198 break; 199 case 's': 200 if (str2sig(optarg, &sig) != 0) 201 errx(EX_USAGE, "invalid signal: %s", optarg); 202 break; 203 case 'h': 204 /* PASSTHROUGH */ 205 default: 206 usage(); 207 /* NORETURN */ 208 } 209 argv += optind; 210 argc -= optind; 211 212 assert(argc >= 0); 213 if (argc == 0) 214 usage(); 215 /* NORETURN */ 216 217 /* 218 * Process named files. 219 */ 220 reqfiles = malloc(argc * sizeof(struct reqfile)); 221 if (reqfiles == NULL) 222 err(EX_OSERR, "malloc()"); 223 nfiles = 0; 224 while (argc--) 225 if (!addfile(*(argv++), &reqfiles[nfiles])) 226 nfiles++; 227 if (nfiles == 0) 228 errx(EX_IOERR, "files not accessible"); 229 230 if (memf != NULL) 231 procstat = procstat_open_kvm(nlistf, memf); 232 else 233 procstat = procstat_open_sysctl(); 234 if (procstat == NULL) 235 errx(1, "procstat_open()"); 236 procs = procstat_getprocs(procstat, KERN_PROC_PROC, 0, &cnt); 237 if (procs == NULL) 238 errx(1, "procstat_getprocs()"); 239 240 /* 241 * Walk through process table and look for matching files. 242 */ 243 for (i = 0; i < cnt; i++) 244 if (procs[i].ki_stat != SZOMB) 245 dofiles(procstat, &procs[i], reqfiles, nfiles); 246 247 for (i = 0; i < nfiles; i++) { 248 fprintf(stderr, "%s:", reqfiles[i].name); 249 fflush(stderr); 250 STAILQ_FOREACH(consumer, &reqfiles[i].consumers, next) { 251 if (consumer->flags != 0) { 252 fprintf(stdout, "%6d", consumer->pid); 253 fflush(stdout); 254 printflags(consumer); 255 if ((flags & UFLAG) != 0) 256 fprintf(stderr, "(%s)", 257 user_from_uid(consumer->uid, 0)); 258 if ((flags & KFLAG) != 0) 259 kill(consumer->pid, sig); 260 fflush(stderr); 261 } 262 } 263 (void)fprintf(stderr, "\n"); 264 } 265 procstat_freeprocs(procstat, procs); 266 procstat_close(procstat); 267 free(reqfiles); 268 return (0); 269 } 270 271 static void 272 dofiles(struct procstat *procstat, struct kinfo_proc *kp, 273 struct reqfile *reqfiles, size_t nfiles) 274 { 275 struct vnstat vn; 276 struct consumer *cons; 277 struct filestat *fst; 278 struct filestat_list *head; 279 int error, match; 280 unsigned int i; 281 char errbuf[_POSIX2_LINE_MAX]; 282 283 head = procstat_getfiles(procstat, kp, flags & MFLAG); 284 if (head == NULL) 285 return; 286 STAILQ_FOREACH(fst, head, next) { 287 if (fst->fs_type != PS_FST_TYPE_VNODE) 288 continue; 289 error = procstat_get_vnode_info(procstat, fst, &vn, errbuf); 290 if (error != 0) 291 continue; 292 for (i = 0; i < nfiles; i++) { 293 if (flags & CFLAG && reqfiles[i].fsid == vn.vn_fsid) { 294 break; 295 } 296 else if (reqfiles[i].fsid == vn.vn_fsid && 297 reqfiles[i].fileid == vn.vn_fileid) { 298 break; 299 } 300 else if (!(flags & FFLAG) && 301 (vn.vn_type == PS_FST_VTYPE_VCHR || 302 vn.vn_type == PS_FST_VTYPE_VBLK) && 303 vn.vn_fsid == reqfiles[i].fileid) { 304 break; 305 } 306 } 307 if (i == nfiles) 308 continue; /* No match. */ 309 310 /* 311 * Look for existing entries. 312 */ 313 match = 0; 314 STAILQ_FOREACH(cons, &reqfiles[i].consumers, next) 315 if (cons->pid == kp->ki_pid) { 316 match = 1; 317 break; 318 } 319 if (match == 1) { /* Use old entry. */ 320 cons->flags |= fst->fs_fflags; 321 cons->uflags |= fst->fs_uflags; 322 } else { 323 /* 324 * Create new entry in the consumer chain. 325 */ 326 cons = calloc(1, sizeof(struct consumer)); 327 if (cons == NULL) { 328 warn("malloc()"); 329 continue; 330 } 331 cons->uid = kp->ki_uid; 332 cons->pid = kp->ki_pid; 333 cons->uflags = fst->fs_uflags; 334 cons->flags = fst->fs_fflags; 335 STAILQ_INSERT_TAIL(&reqfiles[i].consumers, cons, next); 336 } 337 } 338 procstat_freefiles(procstat, head); 339 } 340