1/*
2 * ddev.c -- Darwin device support functions for libproc-based lsof
3 */
4
5
6/*
7 * Portions Copyright 2005 Apple Computer, Inc.  All rights reserved.
8 *
9 * Copyright 2005 Purdue Research Foundation, West Lafayette, Indiana
10 * 47907.  All rights reserved.
11 *
12 * Written by Allan Nathanson, Apple Computer, Inc., and Victor A.
13 * Abell, Purdue University.
14 *
15 * This software is not subject to any license of the American Telephone
16 * and Telegraph Company or the Regents of the University of California.
17 *
18 * Permission is granted to anyone to use this software for any purpose on
19 * any computer system, and to alter it and redistribute it freely, subject
20 * to the following restrictions:
21 *
22 * 1. Neither the authors, nor Apple Computer, Inc. nor Purdue University
23 *    are responsible for any consequences of the use of this software.
24 *
25 * 2. The origin of this software must not be misrepresented, either
26 *    by explicit claim or by omission.  Credit to the authors, Apple
27 *    Computer, Inc. and Purdue University must appear in documentation
28 *    and sources.
29 *
30 * 3. Altered versions must be plainly marked as such, and must not be
31 *    misrepresented as being the original software.
32 *
33 * 4. This notice may not be removed or altered.
34 */
35
36
37#ifndef lint
38static char copyright[] =
39"@(#) Copyright 2005 Apple Computer, Inc. and Purdue Research Foundation.\nAll rights reserved.\n";
40static char *rcsid = "$Id: ddev.c,v 1.2 2006/03/27 23:23:13 abe Exp $";
41#endif
42
43
44#include "lsof.h"
45
46
47/*
48 * Local definitions
49 */
50
51#if	defined(DVCH_DEVPATH)
52#define	DDEV_DEVPATH	DVCH_DEVPATH
53#else	/* !defined(DVCH_DEVPATH) */
54#define	DDEV_DEVPATH	"/dev"
55#endif	/* defined(DVCH_DEVPATH) */
56
57#define	LIKE_BLK_SPEC	"like block special"
58#define	LIKE_CHR_SPEC	"like character special"
59
60#if	defined(USE_STAT)
61#define	STATFN	stat
62#else	/* !defined(USE_STAT) */
63#define	STATFN	lstat
64#endif	/* defined(USE_STAT) */
65
66
67/*
68 * Local static variables.
69 */
70
71static dev_t *ADev = (dev_t *) NULL;	/* device numbers besides DevDev found
72					 * inside DDEV_DEVPATH */
73static int ADevA = 0;			/* entries allocated to ADev[] */
74static int ADevU = 0;			/* entries used in ADev[] */
75
76
77/*
78 * Local function prototypes
79 */
80
81_PROTOTYPE(static int rmdupdev,(struct l_dev ***dp, int n, char *nm));
82_PROTOTYPE(static void saveADev,(struct stat *s));
83
84
85#if	defined(HASSPECDEVD)
86/*
87 * HASSPECDEVD() -- process stat(2) result to see if the device number is
88 *		    inside DDEV_DEVPATH "/"
89 *
90 * exit: s->st_dev changed to DevDev, as required
91 */
92
93void
94HASSPECDEVD(p, s)
95	char *p;			/* file path */
96	struct stat *s;			/* stat(2) result for file */
97{
98	int i;
99
100	switch (s->st_mode & S_IFMT) {
101	case S_IFCHR:
102	case S_IFBLK:
103	    if (s->st_dev == DevDev)
104		return;
105	    (void) readdev(0);
106	    if (!ADev)
107		return;
108	    for (i = 0; i < ADevU; i++) {
109		if (s->st_dev == ADev[i]) {
110		    s->st_dev = DevDev;
111		    return;
112		}
113	    }
114	}
115}
116#endif	/* defined(HASSPECDEVD) */
117
118
119/*
120 * printdevname() -- print character device name
121 */
122
123int
124printdevname(dev, rdev, f, nty)
125	dev_t	*dev;		/* device */
126	dev_t	*rdev;		/* raw device */
127	int	f;		/* 1 = follow with '\n' */
128	int	nty;		/* node type: N_BLK or N_chr */
129{
130	char *cp, *ttl;
131	struct l_dev *dp;
132	int i, len;
133/*
134 * See if the device node resides in DDEV_DEVPATH.  If it does, return zero
135 * to indicate the vnode path is to be used for the NAME column.
136 */
137	if (*dev == DevDev)
138	    return(0);
139	readdev(0);
140	for (i = 0; i < ADevU; i++) {
141	    if (*dev == ADev[i])
142		return(0);
143	}
144/*
145 * This device is not in DDEV_DEVPATH.
146 *
147 * See if it has a DDEV_DEVPATH analogue by searching the device table for a
148 * match without inode number and dev.
149 */
150
151#if	defined(HASBLKDEV)
152	if (nty == N_BLK)
153	    dp = lkupbdev(&DevDev, rdev, 0, 1);
154	else
155#endif	/* defined(HASBLKDEV) */
156
157	    dp = lkupdev(&DevDev, rdev, 0, 1);
158	if (dp) {
159
160	/*
161	 * A match was found.  Record it as a name column addition.
162	 */
163	    ttl = (nty == N_BLK) ? LIKE_BLK_SPEC : LIKE_CHR_SPEC;
164	    len = (int)(1 + strlen(ttl) + 1 + strlen(dp->name) + 1);
165	    if (!(cp = (char *)malloc((MALLOC_S)(len + 1)))) {
166		(void) fprintf(stderr, "%s: no nma space for: (%s %s)\n",
167		    Pn, ttl, dp->name);
168		Exit(1);
169	    }
170	    (void) snpf(cp, len + 1, "(%s %s)", ttl, dp->name);
171	    (void) add_nma(cp, len);
172	    (void) free((MALLOC_P *)cp);
173	}
174/*
175 * Return zero to indicate the vnode path is to be used for the NAME column.
176 */
177	return(0);
178}
179
180
181/*
182 * readdev() -- read device names, modes and types
183 */
184
185void
186readdev(skip)
187	int skip;			/* skip device cache read if 1 --
188					 * ignored since device cache not
189					 * used */
190{
191	DIR *dfp;
192	int dnamlen;
193	struct dirent *dp;
194	char *fp = (char *)NULL;
195	char *path = (char *)NULL;
196	int i = 0;
197	int j = 0;
198	MALLOC_S pl, sz;
199	struct stat sb;
200/*
201 * Read device names but once.
202 */
203	if (Sdev)
204	    return;
205/*
206 * Prepare to scan DDEV_DEVPATH.
207 */
208	Dstkn = Dstkx = 0;
209	Dstk = (char **)NULL;
210	(void) stkdir(DDEV_DEVPATH);
211/*
212 * Unstack the next directory.
213 */
214	while (--Dstkx >= 0) {
215	    if (!(dfp = OpenDir(Dstk[Dstkx]))) {
216
217# if	defined(WARNDEVACCESS)
218		if (!Fwarn) {
219		    (void) fprintf(stderr, "%s: WARNING: can't open: ", Pn);
220		    safestrprt(Dstk[Dstkx], stderr, 1);
221		}
222# endif	/* defined(WARNDEVACCESS) */
223
224		(void) free((FREE_P *)Dstk[Dstkx]);
225		Dstk[Dstkx] = (char *)NULL;
226		continue;
227	    }
228	    if (path) {
229		(void) free((FREE_P *)path);
230		path = (char *)NULL;
231	    }
232	    if (!(path = mkstrcat(Dstk[Dstkx], -1, "/", 1, (char *)NULL, -1,
233				  &pl)))
234	    {
235		(void) fprintf(stderr, "%s: no space for: ", Pn);
236		safestrprt(Dstk[Dstkx], stderr, 1);
237		Exit(1);
238	    }
239	    (void) free((FREE_P *)Dstk[Dstkx]);
240	    Dstk[Dstkx] = (char *)NULL;
241	/*
242	 * Scan the directory.
243	 */
244	    for (dp = ReadDir(dfp); dp; dp = ReadDir(dfp)) {
245		if (dp->d_ino == 0 || dp->d_name[0] == '.')
246		    continue;
247	    /*
248	     * Form the full path name and get its status.
249	     */
250		dnamlen = (int)dp->d_namlen;
251		if (fp) {
252		    (void) free((FREE_P *)fp);
253		    fp = (char *)NULL;
254		}
255		if (!(fp = mkstrcat(path, pl, dp->d_name, dnamlen,
256				    (char *)NULL, -1, (MALLOC_S *)NULL)))
257		{
258		    (void) fprintf(stderr, "%s: no space for: ", Pn);
259		    safestrprt(path, stderr, 0);
260		    safestrprtn(dp->d_name, dnamlen, stderr, 1);
261		    Exit(1);
262		}
263		if (STATFN(fp, &sb) != 0) {
264		    if (errno == ENOENT)	/* a sym link to nowhere? */
265			continue;
266
267# if	defined(WARNDEVACCESS)
268		    if (!Fwarn) {
269			int errno_save = errno;
270
271			(void) fprintf(stderr, "%s: can't stat ", Pn);
272			safestrprt(fp, stderr, 0);
273			(void) fprintf(stderr, ": %s\n", strerror(errno_save));
274		    }
275# endif	/* defined(WARNDEVACCESS) */
276
277		    continue;
278		}
279	    /*
280	     * If it's a subdirectory, stack its name for later
281	     * processing.
282	     */
283		if ((sb.st_mode & S_IFMT) == S_IFDIR) {
284
285		/*
286		 * Skip /dev/fd.
287		 */
288		    if (strcmp(fp, "/dev/fd"))
289			(void) stkdir(fp);
290		    continue;
291		}
292		if ((sb.st_mode & S_IFMT) == S_IFLNK) {
293
294		/*
295		 * Ignore symbolic links.
296		 */
297		    continue;
298		}
299		if ((sb.st_mode & S_IFMT) == S_IFCHR) {
300
301		/*
302		 * Save character device information in Devtp[].
303		 */
304		    if (i >= Ndev) {
305			Ndev += DEVINCR;
306			if (!Devtp)
307			    Devtp = (struct l_dev *)malloc(
308				    (MALLOC_S)(sizeof(struct l_dev)*Ndev));
309			else
310			    Devtp = (struct l_dev *)realloc((MALLOC_P *)Devtp,
311				    (MALLOC_S)(sizeof(struct l_dev)*Ndev));
312			if (!Devtp) {
313			    (void) fprintf(stderr,
314				"%s: no space for character device\n", Pn);
315			    Exit(1);
316			}
317		    }
318		    Devtp[i].rdev = sb.st_rdev;
319		    Devtp[i].inode = (INODETYPE)sb.st_ino;
320		    if (!(Devtp[i].name = mkstrcpy(fp, (MALLOC_S *)NULL))) {
321			(void) fprintf(stderr,
322			    "%s: no space for device name: ", Pn);
323			safestrprt(fp, stderr, 1);
324			Exit(1);
325		    }
326		    Devtp[i].v = 0;
327		    i++;
328		}
329
330# if	defined(HASBLKDEV)
331		if ((sb.st_mode & S_IFMT) == S_IFBLK) {
332
333		/*
334		 * Save block device information in BDevtp[].
335		 */
336		    if (j >= BNdev) {
337			BNdev += DEVINCR;
338			if (!BDevtp)
339			    BDevtp = (struct l_dev *)malloc(
340				     (MALLOC_S)(sizeof(struct l_dev)*BNdev));
341			else
342			    BDevtp = (struct l_dev *)realloc((MALLOC_P *)BDevtp,
343				     (MALLOC_S)(sizeof(struct l_dev)*BNdev));
344			if (!BDevtp) {
345			    (void) fprintf(stderr,
346				"%s: no space for block device\n", Pn);
347			    Exit(1);
348			}
349		    }
350		    BDevtp[j].name = fp;
351		    fp = (char *)NULL;
352		    BDevtp[j].inode = (INODETYPE)sb.st_ino;
353		    BDevtp[j].rdev = sb.st_rdev;
354		    BDevtp[j].v = 0;
355		    j++;
356		}
357# endif	/* defined(HASBLKDEV) */
358
359	    /*
360	     * Save a possible new st_dev number within DDEV_DEVPATH.
361	     */
362		if (sb.st_dev != DevDev)
363		    (void) saveADev(&sb);
364	    }
365	    (void) CloseDir(dfp);
366	}
367/*
368 * Free any unneeded space that was allocated.
369 */
370	if (ADev && (ADevU < ADevA)) {
371
372	/*
373	 * Reduce space allocated to additional DDEV_DEVPATH device numbers.
374	 */
375	    if (!ADevU) {
376
377	    /*
378	     * If no space was used, free the entire allocation.
379	     */
380		(void) free((FREE_P *)ADev);
381		ADev = (dev_t *)NULL;
382		ADevA = 0;
383	    } else {
384
385	    /*
386	     * Reduce the allocation to what was used.
387	     */
388		sz = (MALLOC_S)(ADevU * sizeof(dev_t));
389		if (!(ADev = (dev_t *)realloc((MALLOC_P *)ADev, sz))) {
390		    (void) fprintf(stderr, "%s: can't reduce ADev[]\n", Pn);
391		    Exit(1);
392		}
393	    }
394	}
395	if (!Dstk) {
396	    (void) free((FREE_P *)Dstk);
397	    Dstk = (char **)NULL;
398	}
399	if (fp)
400	    (void) free((FREE_P *)fp);
401	if (path)
402	    (void) free((FREE_P *)path);
403
404# if	defined(HASBLKDEV)
405/*
406 * Reduce the BDevtp[] (optional) and Devtp[] tables to their minimum
407 * sizes; allocate and build sort pointer lists; and sort the tables by
408 * device number.
409 */
410	if (BNdev) {
411	    if (BNdev > j) {
412		BNdev = j;
413		BDevtp = (struct l_dev *)realloc((MALLOC_P *)BDevtp,
414			 (MALLOC_S)(sizeof(struct l_dev) * BNdev));
415	    }
416	    if (!(BSdev = (struct l_dev **)malloc(
417			  (MALLOC_S)(sizeof(struct l_dev *) * BNdev))))
418	    {
419		(void) fprintf(stderr,
420		    "%s: no space for block device sort pointers\n", Pn);
421		Exit(1);
422	    }
423	    for (j = 0; j < BNdev; j++) {
424		BSdev[j] = &BDevtp[j];
425	    }
426	    (void) qsort((QSORT_P *)BSdev, (size_t)BNdev,
427		(size_t)sizeof(struct l_dev *), compdev);
428	    BNdev = rmdupdev(&BSdev, BNdev, "block");
429	}
430
431#  if	!defined(NOWARNBLKDEV)
432	else {
433	    if (!Fwarn)
434		(void) fprintf(stderr,
435		    "%s: WARNING: no block devices found\n", Pn);
436	}
437#  endif	/* !defined(NOWARNBLKDEV) */
438# endif	/* defined(HASBLKDEV) */
439
440	if (Ndev) {
441	    if (Ndev > i) {
442		Ndev = i;
443		Devtp = (struct l_dev *)realloc((MALLOC_P *)Devtp,
444			(MALLOC_S)(sizeof(struct l_dev) * Ndev));
445	    }
446	    if (!(Sdev = (struct l_dev **)malloc(
447			 (MALLOC_S)(sizeof(struct l_dev *) * Ndev))))
448	    {
449		(void) fprintf(stderr,
450		    "%s: no space for character device sort pointers\n", Pn);
451		Exit(1);
452	    }
453	    for (i = 0; i < Ndev; i++) {
454		Sdev[i] = &Devtp[i];
455	    }
456	    (void) qsort((QSORT_P *)Sdev, (size_t)Ndev,
457		(size_t)sizeof(struct l_dev *), compdev);
458	    Ndev = rmdupdev(&Sdev, Ndev, "char");
459	} else {
460	    (void) fprintf(stderr, "%s: no character devices found\n", Pn);
461	    Exit(1);
462	}
463}
464
465
466/*
467 * rmdupdev() - remove duplicate (major/minor/inode) devices
468 */
469
470static int
471rmdupdev(dp, n, nm)
472	struct l_dev ***dp;	/* device table pointers address */
473	int n;			/* number of pointers */
474	char *nm;		/* device table name for error message */
475{
476	int i, j, k;
477	struct l_dev **p;
478
479	for (i = j = 0, p = *dp; i < n ;) {
480	    for (k = i + 1; k < n; k++) {
481		if (p[i]->rdev != p[k]->rdev || p[i]->inode != p[k]->inode)
482		    break;
483	    }
484	    if (i != j)
485		p[j] = p[i];
486	    j++;
487	    i = k;
488	}
489	if (n == j)
490	    return(n);
491	if (!(*dp = (struct l_dev **)realloc((MALLOC_P *)*dp,
492		    (MALLOC_S)(j * sizeof(struct l_dev *)))))
493	{
494	    (void) fprintf(stderr, "%s: can't realloc %s device pointers\n",
495		Pn, nm);
496	    Exit(1);
497	}
498	return(j);
499}
500
501
502/*
503 * saveADev() - save additional device number appearing inside DDEV_DEVPATH
504 */
505
506static void
507saveADev(s)
508	struct stat *s;			/* stat(2) buffer for file */
509{
510	int i;
511	MALLOC_S sz;
512/*
513 * Process VCHR files.
514 *
515 * Optionally process VBLK files.
516 */
517
518#if	defined(HASBLKDEV)
519	if (((s->st_mode & S_IFMT) != S_IFBLK)
520	&&  ((s->st_mode & S_IFMT) != S_IFCHR))
521#else	/* !defined(HASBLKDEV) */
522	if ((s->st_mode & S_IFCHR) != S_IFCHR)
523#endif	/* defined(HASBLKDEV) */
524
525		return;
526/*
527 * See if this is a new VBLK or VCHR st_dev value for ADev[].
528 */
529	for (i = 0; i < ADevU; i++) {
530	    if (s->st_dev == ADev[i])
531		return;
532	}
533/*
534 * This is a new device number to add to ADev[].
535 */
536	if (ADevU >= ADevA) {
537	    ADevA += 16;
538	    sz = (MALLOC_S)(ADevA * sizeof(dev_t));
539	    if (ADev)
540		ADev = (dev_t *)realloc((MALLOC_P *)ADev, sz);
541	    else
542		ADev = (dev_t *)malloc(sz);
543	    if (!ADev) {
544		(void) fprintf(stderr, "%s: no space for ADev[]\n", Pn);
545		Exit(1);
546	    }
547	}
548	ADev[ADevU++] = s->st_dev;
549}
550