/* device name <-> number map system optimized for rapid, constant time lookup.
   Copyright Charles Blake, 1996, see COPYING for details.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>

#define __KERNEL__
#include <linux/kdev_t.h>
#undef __KERNEL__

#define DEVDIR		"/dev"
#define DEVTAB		"psdevtab"
static char *devtab_paths[] = {
    "/etc/" DEVTAB,
    "%s/."  DEVTAB,
    NULL
};
#define DEVINITIALMODE	0664
#define DEV_MAX_PATH	(5+256)
#define DEV_NAME_MAX	8

static dev_t major[] = { 2, 3, 4, 5, 19, 20 };
#define Nminor		256
#define Nmajor		(sizeof(major)/sizeof(dev_t))
#define Ndev		(Nmajor*Nminor)
#define Ndevtab		(Ndev*DEV_NAME_MAX)

static char* devtab;	/* the memory buffer holding all the name strings */

/* This macro evaluates to the address into the table of the string of
   DEV_NAME_MAX chars for the device with major m, minor n. */
#define TAB(m,n) (devtab + (m)*(Nminor*DEV_NAME_MAX) + (n)*DEV_NAME_MAX)

static int devtab_initialized = 0;

static char* name_to_path(char* name);	/* forward declarations */
static int   init_devtab (void);

/* Device Name -> Number Map
   many-to-one: -1 on failed match.
*/
dev_t name_to_dev(char* name) {
    static struct stat sbuf;
    return (stat(name_to_path(name), &sbuf) < 0) ? -1 : sbuf.st_rdev;
}

/* find m in a[] assuming a is sorted into ascending order */
/* in-line linear search placeholder until more majors warrant binary search */
static __inline__ int lookup(dev_t m, dev_t* a, int n) {
    int k;
    for(k=0; k < n && a[k] != m; k++)
	;
    return (k < n) ? k : -1;
}

/* Device Number -> Name Map
   one-to-many: first directory order match in DEVDIR, "" on failed match.
*/
char* dev_to_name(dev_t num) {
    static char	rval[DEV_NAME_MAX+1];
    dev_t m = MAJOR(num), n = MINOR(num), tmp;
    if (!devtab_initialized && !init_devtab())
	return "";
    if ((tmp = lookup(m, major, Nmajor)) == (dev_t)-1)
	return "";
    strncpy(rval, TAB(tmp,n), DEV_NAME_MAX);
    rval[DEV_NAME_MAX] = '\0';
    return rval;
}

static int dev_to_devtab(int);

static int init_devtab(void) {
    static struct stat sbuf, lbuf;
    static int fd;
    char **fmt, path[64], *HOME = getenv("HOME") ? getenv("HOME") : "";
    for (fmt = devtab_paths; *fmt; fmt++) {
	snprintf(path, sizeof path, *fmt, HOME);
	lbuf.st_ino = 0; /* initialize for test later */
	if (lstat(path, &lbuf) >= 0 && S_ISLNK(lbuf.st_mode))
	    /* don't follow symlinks */
	    continue;
	if ( (fd = open(path, O_RDONLY)) < 0	/* open DEVTAB file */
	     || fstat(fd, &sbuf) < 0		/* fstat it */
	     || (lbuf.st_ino && (sbuf.st_ino != lbuf.st_ino)) /* race */
	     || sbuf.st_nlink > 1		/* hardlink attack */
	     || sbuf.st_size != Ndevtab		/* make sure it's the right size */
	     || (devtab = mmap(0, Ndevtab, PROT_READ, MAP_SHARED, fd, 0)) == (caddr_t) -1
	     || close(fd) == -1)
	    {	/* could not open for read, attempt to fix/create */
		int oumsk = umask(0);
		if (devtab)
		    munmap(devtab, Ndevtab);
		if (((fd = open(path, O_RDWR|O_TRUNC|O_CREAT, DEVINITIALMODE)) == -1 &&
		     (unlink(path), fd = open(path, O_RDWR|O_TRUNC|O_CREAT, DEVINITIALMODE)) == -1)
		     || !dev_to_devtab(fd)) {
		    close(fd);          /* either both opens failed or the constructor failed */
		    unlink(path);	/* in case we created but could not fill a file */
		    umask(oumsk);
		    continue;
		} else {
		    devtab_initialized = 1;
		    close(fd);
		    umask(oumsk);
		    return 1;
		}
	    }
	else
	    return devtab_initialized = 1;
    }
    return devtab_initialized;
}

/* stat every file in DEVDIR saving its basename in devtab[] if it has
   a MAJOR(st_rdev) in our list of majors. return 0 on error otherwise 1. */
static int dev_to_devtab(int fd) {
    static struct stat sbuf;
    int i;
    dev_t m;
    struct dirent* ent;
    DIR* dev;
    
    if (!(dev = opendir(DEVDIR))) {
	fprintf(stderr, "%s: %s\nCannot generate device number -> name mapping.\n",
		DEVDIR, sys_errlist[errno]);
	return 0;
    }
    if (!(devtab = malloc(Ndevtab))) {
	fprintf(stderr, "%s: could not allocate memory\n", sys_errlist[errno]);
	return 0;
    }
    memset((void*)devtab, 0, Ndevtab);
    while ((ent = readdir(dev))) {	/* loop over all dirents in DEVDIR */
	if (lstat(name_to_path(ent->d_name), &sbuf) < 0
	    || !S_ISCHR(sbuf.st_mode))	    /* only look for char special devs */
	    continue;			    /* due to overloading of majors */
	m = MAJOR(sbuf.st_rdev);	    /* copy name to appropriate spot */
	if ((i = lookup(m, major, Nmajor)) != -1)
	    strncpy(TAB(i,MINOR(sbuf.st_rdev)), ent->d_name, DEV_NAME_MAX);
    }
    closedir(dev);
    if (write(fd, devtab, Ndevtab) != Ndevtab) /* probably no disk space */
	return 0;
    return 1;
}

static char path[DEV_MAX_PATH];

static char* name_to_path(char* name) {
    static char* Path;
    if (!Path) {
	strcpy(path, DEVDIR);		/* copy DEVDIR */
	path[sizeof(DEVDIR) - 1] = '/';	/* change NUL to '/' */
	Path = path + sizeof(DEVDIR);	/* put Path at start of basename */
    }
    strncpy(Path, name, DEV_MAX_PATH - sizeof(DEVDIR));
    return path;
}

#ifdef TEST_DEVNAME
int main(int argc, char** argv) {	/* usage: cmd [<major> <minor>|<name>] */
    dev_t tmp;
    if (argc < 2) {
	printf("%s: [ maj min... | name ... ]\n", argv[0]);
	return 0;
    }
    if (argv[1][0] >= '0' && argv[1][0] <= '9')
	for(argv++ ; argv[0] && argv[1] ; argv+=2)
	    printf("%s\n", dev_to_name(MKDEV( atoi(argv[0]), atoi(argv[1]) )));
    else
	for(argv++ ; *argv ; argv++) {
	    tmp = name_to_dev(*argv);
	    printf("%d, %d\n", MAJOR(tmp), MINOR(tmp));
	}
    return 0;
}
#endif
/*
Using this program on over 700 files in /dev to perform number->name resolution
took well under 300 microsecs per device number pair on a Pentium 90.  It is
somewhat tough to time because once the 3 pages have been mapped in, the time is
almost zero.  For things like top, this method may even be faster in the long
run.  Those interested can gprof it for me.  This system has the virtue of being
nearly perfectly adaptable to individual systems, self updating when /dev
changes and pretty darn fast when it hasn't.  It will be slow for users without
perms to change the psdevtab file, though.  So this is what I decided was
reasonable.  If the process does not have perms to create or update
/etc/psdevtab and it is out of date, we try /tmp/psdevtab.  If /tmp/psdevtab is
either out of date or unreadable (malicious user creates it and chmods it),
$HOME/.psdevtab is used.  This secondarily allows for per-user naming of ttys,
but is really so that at most one user sees only a single delay per /dev
modification.

To do the timings I did something like this with zsh:
    a=(`ls -l *(%^@/) | awk '{print $5 $6}' | sed 's/,/ /'`);
    time ./test $a

Finally, for lack of a better file for these to be in, I have collected the
old algorithmic device number <-> device name mappings.
  Let m = major device number and n = minor device number satisfy:
    devno = m<<8 + n , m = devno>>8 , n = devno && 0x00FF, and let
    char L[32]="pqrstuvwxyzABCDEFGHIJKLMNOPQRSTU", H[16]="01234567890abcdef";
	DEVICE NUMBERS		SPECIAL FILE NAMES
  OLD SYSTEM (64 pseudoterminal devices):
	m=4:
	    n=0..63:	tty + itoa_dec(n+1)
	    n=128..191:	pty + L[(n-128)/16] + H[(n-128)%16]
	    n=192..255:	tty + L[(n-192)/16] + H[(n-192)%16]
  NEW SYSTEM (256/512 pseudoterminal devices):
	m=2, n:		pty + L[n/16] + H[n%16]
	m=3, n:		tty + L[n/16] + H[n%16]
	m=4, n:		tty + itoa_dec(n+1)
	m=49, n:	pty + L[16+n/16] + H[n%16]
	m=50, n:	tty + L[16+n/16] + H[n%16]
  (THE SAME IN EITHER SYSTEM)
  CALL-UNIX AND CONTROLLING TERMINAL DEVICES
	m=5:
	    n=0:	tty
	    n=64..128:	cua  + {'0' + (n-64)}
  CYCLADES MULTIPORT:
	m=19, n:	ttyC + itoa_hex(n)
	m=20, n: cub + itoa_hex(n) */

/* Re-implementation of old interface with the new generic functions. */

/* This does exactly the same thing as name_to_dev only now a full "ttyXX"
   specification will work as well.
*/
int tty_to_dev(char *tty) {
    static char pref_name_1[32] = "tty", *pnam1 = pref_name_1 + 3,
	        pref_name_2[32] = "cu",  *pnam2 = pref_name_2 + 2;
    dev_t num;
    if ((num = name_to_dev(tty)) != (dev_t) -1)	/* try tty straight up */
	return num;
    strncpy(pnam1, tty, 32 - 3);	/* try with "tty" prepended */
    if ((num = name_to_dev(pref_name_1)) != (dev_t) -1)
	return num;
    strncpy(pnam2, tty, 32 - 2);	/* try with "cu" prepended */
    if ((num = name_to_dev(pref_name_2)) != (dev_t) -1)
	return num;
    return -1;		/* no match */
}

/* new abstraction that can maybe be generalized a little better. */
char* abbrev_of_tty(char *tty) {
    static char temp[32];		/* return buf: good only until next call */
    char *pos = strpbrk(tty, "yu");	/* end of (presumed) prefices: tty*, cu* */
    temp[0] = 0;
    if (tty && tty[0] && pos && pos[0] && pos[1])
	sprintf(temp, "%*.*s", 3, 3, pos + 1);
    else
	strncpy(temp, " ? ", 31);
    return temp;
}

/* Do in-place modification of the 4-buffer `tty' based upon `dev' */
void dev_to_tty(char *tty, int dev) {
    char* new = abbrev_of_tty(dev_to_name(dev));
    strncpy(tty, new, 4);
}
