1221807Sstas/*- 2221807Sstas * Copyright (c) 2005-2009 Stanislav Sedov <stas@FreeBSD.org> 3221807Sstas * All rights reserved. 4221807Sstas * 5221807Sstas * Redistribution and use in source and binary forms, with or without 6221807Sstas * modification, are permitted provided that the following conditions 7221807Sstas * are met: 8221807Sstas * 1. Redistributions of source code must retain the above copyright 9221807Sstas * notice, this list of conditions and the following disclaimer. 10221807Sstas * 2. Redistributions in binary form must reproduce the above copyright 11221807Sstas * notice, this list of conditions and the following disclaimer in the 12221807Sstas * documentation and/or other materials provided with the distribution. 13221807Sstas * 14221807Sstas * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15221807Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16221807Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17221807Sstas * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18221807Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19221807Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20221807Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21221807Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22221807Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23221807Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24221807Sstas * SUCH DAMAGE. 25221807Sstas * 26221807Sstas */ 27221807Sstas#include <sys/cdefs.h> 28221807Sstas__FBSDID("$FreeBSD$"); 29221807Sstas 30221807Sstas#include <sys/queue.h> 31221807Sstas#include <sys/stat.h> 32221807Sstas#include <sys/sysctl.h> 33221807Sstas#include <sys/user.h> 34221807Sstas 35221807Sstas#include <assert.h> 36221807Sstas#include <ctype.h> 37221807Sstas#include <err.h> 38221807Sstas#include <fcntl.h> 39221807Sstas#include <libprocstat.h> 40221807Sstas#include <limits.h> 41221807Sstas#include <paths.h> 42221807Sstas#include <pwd.h> 43221807Sstas#include <signal.h> 44221807Sstas#include <stdio.h> 45221807Sstas#include <stdlib.h> 46221807Sstas#include <string.h> 47221807Sstas#include <sysexits.h> 48221807Sstas#include <unistd.h> 49221807Sstas 50221807Sstas#include "functions.h" 51221807Sstas 52221807Sstas/* 53221807Sstas * File access mode flags table. 54221807Sstas */ 55227239Sedstatic const struct { 56221807Sstas int flag; 57221807Sstas char ch; 58221807Sstas} fflags[] = { 59221807Sstas {PS_FST_FFLAG_WRITE, 'w'}, 60221807Sstas {PS_FST_FFLAG_APPEND, 'a'}, 61221807Sstas {PS_FST_FFLAG_DIRECT, 'd'}, 62221807Sstas {PS_FST_FFLAG_SHLOCK, 's'}, 63221807Sstas {PS_FST_FFLAG_EXLOCK, 'e'} 64221807Sstas}; 65221807Sstas#define NFFLAGS (sizeof(fflags) / sizeof(*fflags)) 66221807Sstas 67221807Sstas/* 68221807Sstas * Usage flags translation table. 69221807Sstas */ 70227239Sedstatic const struct { 71221807Sstas int flag; 72221807Sstas char ch; 73221807Sstas} uflags[] = { 74221807Sstas {PS_FST_UFLAG_RDIR, 'r'}, 75221807Sstas {PS_FST_UFLAG_CDIR, 'c'}, 76221807Sstas {PS_FST_UFLAG_JAIL, 'j'}, 77221807Sstas {PS_FST_UFLAG_TRACE, 't'}, 78221807Sstas {PS_FST_UFLAG_TEXT, 'x'}, 79221807Sstas {PS_FST_UFLAG_MMAP, 'm'}, 80221807Sstas {PS_FST_UFLAG_CTTY, 'y'} 81221807Sstas}; 82221807Sstas#define NUFLAGS (sizeof(uflags) / sizeof(*uflags)) 83221807Sstas 84221807Sstasstruct consumer { 85221807Sstas pid_t pid; 86221807Sstas uid_t uid; 87221807Sstas int fd; 88221807Sstas int flags; 89221807Sstas int uflags; 90221807Sstas STAILQ_ENTRY(consumer) next; 91221807Sstas}; 92221807Sstasstruct reqfile { 93221807Sstas uint32_t fsid; 94221807Sstas uint64_t fileid; 95221807Sstas const char *name; 96221807Sstas STAILQ_HEAD(, consumer) consumers; 97221807Sstas}; 98221807Sstas 99221807Sstas/* 100221807Sstas * Option flags. 101221807Sstas */ 102221807Sstas#define UFLAG 0x01 /* -u flag: show users */ 103221807Sstas#define FFLAG 0x02 /* -f flag: specified files only */ 104221807Sstas#define CFLAG 0x04 /* -c flag: treat as mpoints */ 105221807Sstas#define MFLAG 0x10 /* -m flag: mmapped files too */ 106221807Sstas#define KFLAG 0x20 /* -k flag: send signal (SIGKILL by default) */ 107221807Sstas 108221807Sstasstatic int flags = 0; /* Option flags. */ 109221807Sstas 110221807Sstasstatic void printflags(struct consumer *consumer); 111221807Sstasstatic int str2sig(const char *str); 112221807Sstasstatic void usage(void) __dead2; 113221807Sstasstatic int addfile(const char *path, struct reqfile *reqfile); 114221807Sstasstatic void dofiles(struct procstat *procstat, struct kinfo_proc *kp, 115221807Sstas struct reqfile *reqfiles, size_t nfiles); 116221807Sstas 117221807Sstasstatic void 118221807Sstasusage(void) 119221807Sstas{ 120221807Sstas 121221807Sstas fprintf(stderr, 122221807Sstas"usage: fuser [-cfhkmu] [-M core] [-N system] [-s signal] file ...\n"); 123221807Sstas exit(EX_USAGE); 124221807Sstas} 125221807Sstas 126221807Sstasstatic void 127221807Sstasprintflags(struct consumer *cons) 128221807Sstas{ 129221807Sstas unsigned int i; 130221807Sstas 131221807Sstas assert(cons); 132221807Sstas for (i = 0; i < NUFLAGS; i++) 133221807Sstas if ((cons->uflags & uflags[i].flag) != 0) 134221807Sstas fputc(uflags[i].ch, stderr); 135221807Sstas for (i = 0; i < NFFLAGS; i++) 136221807Sstas if ((cons->flags & fflags[i].flag) != 0) 137221807Sstas fputc(fflags[i].ch, stderr); 138221807Sstas} 139221807Sstas 140221807Sstas/* 141221807Sstas * Add file to the list. 142221807Sstas */ 143221807Sstasstatic int 144221807Sstasaddfile(const char *path, struct reqfile *reqfile) 145221807Sstas{ 146221807Sstas struct stat sb; 147221807Sstas 148221807Sstas assert(path); 149221807Sstas if (stat(path, &sb) != 0) { 150221807Sstas warn("%s", path); 151221807Sstas return (1); 152221807Sstas } 153221807Sstas reqfile->fileid = sb.st_ino; 154221807Sstas reqfile->fsid = sb.st_dev; 155221807Sstas reqfile->name = path; 156221807Sstas STAILQ_INIT(&reqfile->consumers); 157221807Sstas return (0); 158221807Sstas} 159221807Sstas 160221807Sstasint 161221807Sstasdo_fuser(int argc, char *argv[]) 162221807Sstas{ 163221807Sstas struct consumer *consumer; 164221807Sstas struct kinfo_proc *p, *procs; 165221807Sstas struct procstat *procstat; 166221807Sstas struct reqfile *reqfiles; 167221807Sstas char *ep, *nlistf, *memf; 168221807Sstas int ch, cnt, sig; 169221807Sstas unsigned int i, nfiles; 170221807Sstas 171221807Sstas sig = SIGKILL; /* Default to kill. */ 172221807Sstas nlistf = NULL; 173221807Sstas memf = NULL; 174221807Sstas while ((ch = getopt(argc, argv, "M:N:cfhkms:u")) != -1) 175221807Sstas switch(ch) { 176221807Sstas case 'f': 177221807Sstas if ((flags & CFLAG) != 0) 178221807Sstas usage(); 179221807Sstas flags |= FFLAG; 180221807Sstas break; 181221807Sstas case 'c': 182221807Sstas if ((flags & FFLAG) != 0) 183221807Sstas usage(); 184221807Sstas flags |= CFLAG; 185221807Sstas break; 186221807Sstas case 'N': 187221807Sstas nlistf = optarg; 188221807Sstas break; 189221807Sstas case 'M': 190221807Sstas memf = optarg; 191221807Sstas break; 192221807Sstas case 'u': 193221807Sstas flags |= UFLAG; 194221807Sstas break; 195221807Sstas case 'm': 196221807Sstas flags |= MFLAG; 197221807Sstas break; 198221807Sstas case 'k': 199221807Sstas flags |= KFLAG; 200221807Sstas break; 201221807Sstas case 's': 202221807Sstas if (isdigit(*optarg)) { 203221807Sstas sig = strtol(optarg, &ep, 10); 204221807Sstas if (*ep != '\0' || sig < 0 || sig >= sys_nsig) 205221807Sstas errx(EX_USAGE, "illegal signal number" ": %s", 206221807Sstas optarg); 207221807Sstas } else { 208221807Sstas sig = str2sig(optarg); 209221807Sstas if (sig < 0) 210221807Sstas errx(EX_USAGE, "illegal signal name: " 211221807Sstas "%s", optarg); 212221807Sstas } 213221807Sstas break; 214221807Sstas case 'h': 215221807Sstas /* PASSTHROUGH */ 216221807Sstas default: 217221807Sstas usage(); 218221807Sstas /* NORETURN */ 219221807Sstas } 220221807Sstas argv += optind; 221221807Sstas argc -= optind; 222221807Sstas 223221807Sstas assert(argc >= 0); 224221807Sstas if (argc == 0) 225221807Sstas usage(); 226221807Sstas /* NORETURN */ 227221807Sstas 228221807Sstas /* 229221807Sstas * Process named files. 230221807Sstas */ 231221807Sstas reqfiles = malloc(argc * sizeof(struct reqfile)); 232221807Sstas if (reqfiles == NULL) 233221807Sstas err(EX_OSERR, "malloc()"); 234221807Sstas nfiles = 0; 235221807Sstas while (argc--) 236221807Sstas if (!addfile(*(argv++), &reqfiles[nfiles])) 237221807Sstas nfiles++; 238221807Sstas if (nfiles == 0) 239221807Sstas errx(EX_IOERR, "files not accessible"); 240221807Sstas 241221807Sstas if (memf != NULL) 242221807Sstas procstat = procstat_open_kvm(nlistf, memf); 243221807Sstas else 244221807Sstas procstat = procstat_open_sysctl(); 245221807Sstas if (procstat == NULL) 246221807Sstas errx(1, "procstat_open()"); 247221807Sstas procs = procstat_getprocs(procstat, KERN_PROC_PROC, 0, &cnt); 248221807Sstas if (procs == NULL) 249221807Sstas errx(1, "procstat_getprocs()"); 250221807Sstas 251221807Sstas /* 252221807Sstas * Walk through process table and look for matching files. 253221807Sstas */ 254221807Sstas p = procs; 255221807Sstas while(cnt--) 256221807Sstas if (p->ki_stat != SZOMB) 257221807Sstas dofiles(procstat, p++, reqfiles, nfiles); 258221807Sstas 259221807Sstas for (i = 0; i < nfiles; i++) { 260221807Sstas fprintf(stderr, "%s:", reqfiles[i].name); 261221807Sstas fflush(stderr); 262221807Sstas STAILQ_FOREACH(consumer, &reqfiles[i].consumers, next) { 263221807Sstas if (consumer->flags != 0) { 264221807Sstas fprintf(stdout, "%6d", consumer->pid); 265221807Sstas fflush(stdout); 266221807Sstas printflags(consumer); 267221807Sstas if ((flags & UFLAG) != 0) 268221807Sstas fprintf(stderr, "(%s)", 269221807Sstas user_from_uid(consumer->uid, 0)); 270221807Sstas if ((flags & KFLAG) != 0) 271221807Sstas kill(consumer->pid, sig); 272221807Sstas fflush(stderr); 273221807Sstas } 274221807Sstas } 275221807Sstas (void)fprintf(stderr, "\n"); 276221807Sstas } 277221807Sstas procstat_freeprocs(procstat, procs); 278221807Sstas procstat_close(procstat); 279221807Sstas free(reqfiles); 280221807Sstas return (0); 281221807Sstas} 282221807Sstas 283221807Sstasstatic void 284221807Sstasdofiles(struct procstat *procstat, struct kinfo_proc *kp, 285221807Sstas struct reqfile *reqfiles, size_t nfiles) 286221807Sstas{ 287221807Sstas struct vnstat vn; 288221807Sstas struct consumer *cons; 289221807Sstas struct filestat *fst; 290221807Sstas struct filestat_list *head; 291221807Sstas int error, match; 292221807Sstas unsigned int i; 293221807Sstas char errbuf[_POSIX2_LINE_MAX]; 294221807Sstas 295221807Sstas head = procstat_getfiles(procstat, kp, flags & MFLAG); 296221807Sstas if (head == NULL) 297221807Sstas return; 298221807Sstas STAILQ_FOREACH(fst, head, next) { 299221807Sstas if (fst->fs_type != PS_FST_TYPE_VNODE) 300221807Sstas continue; 301221807Sstas error = procstat_get_vnode_info(procstat, fst, &vn, errbuf); 302221807Sstas if (error != 0) 303221807Sstas continue; 304221807Sstas for (i = 0; i < nfiles; i++) { 305221807Sstas if (flags & CFLAG && reqfiles[i].fsid == vn.vn_fsid) { 306221807Sstas break; 307221807Sstas } 308221807Sstas else if (reqfiles[i].fsid == vn.vn_fsid && 309221807Sstas reqfiles[i].fileid == vn.vn_fileid) { 310221807Sstas break; 311221807Sstas } 312221807Sstas else if (!(flags & FFLAG) && 313221807Sstas (vn.vn_type == PS_FST_VTYPE_VCHR || 314221807Sstas vn.vn_type == PS_FST_VTYPE_VBLK) && 315221807Sstas vn.vn_fsid == reqfiles[i].fileid) { 316221807Sstas break; 317221807Sstas } 318221807Sstas } 319221807Sstas if (i == nfiles) 320221807Sstas continue; /* No match. */ 321221807Sstas 322221807Sstas /* 323221807Sstas * Look for existing entries. 324221807Sstas */ 325221807Sstas match = 0; 326221807Sstas STAILQ_FOREACH(cons, &reqfiles[i].consumers, next) 327221807Sstas if (cons->pid == kp->ki_pid) { 328221807Sstas match = 1; 329221807Sstas break; 330221807Sstas } 331221807Sstas if (match == 1) { /* Use old entry. */ 332221807Sstas cons->flags |= fst->fs_fflags; 333221807Sstas cons->uflags |= fst->fs_uflags; 334221807Sstas } else { 335221807Sstas /* 336221807Sstas * Create new entry in the consumer chain. 337221807Sstas */ 338221807Sstas cons = calloc(1, sizeof(struct consumer)); 339221807Sstas if (cons == NULL) { 340221807Sstas warn("malloc()"); 341221807Sstas continue; 342221807Sstas } 343221807Sstas cons->uid = kp->ki_uid; 344221807Sstas cons->pid = kp->ki_pid; 345221807Sstas cons->uflags = fst->fs_uflags; 346221807Sstas cons->flags = fst->fs_fflags; 347221807Sstas STAILQ_INSERT_TAIL(&reqfiles[i].consumers, cons, next); 348221807Sstas } 349221807Sstas } 350221807Sstas procstat_freefiles(procstat, head); 351221807Sstas} 352221807Sstas 353221807Sstas/* 354221807Sstas * Returns signal number for it's string representation. 355221807Sstas */ 356221807Sstasstatic int 357221807Sstasstr2sig(const char *str) 358221807Sstas{ 359221807Sstas int i; 360221807Sstas 361223271Sjilles if (!strncasecmp(str, "SIG", 3)) 362223271Sjilles str += 3; 363221807Sstas for (i = 1; i < sys_nsig; i++) { 364221807Sstas if (!strcasecmp(sys_signame[i], str)) 365221807Sstas return (i); 366221807Sstas } 367221807Sstas return (-1); 368221807Sstas} 369