/* 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 <asm/string.h> */ /* nice inline assembly string fns */
#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		"/etc/psdevtab"
#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.
      Accuracy is guaranteed through expensive stat's only when DEVTAB is
      older than DEVDIR when this function is first called.
*/
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(void);

static int init_devtab(void) {
    static struct stat sbuf, sbuf2;
    static int fd;
    stat(DEVDIR, &sbuf2);			/* stat DEVDIR */
    if ( (fd = open(DEVTAB, O_RDONLY)) < 0	/* open DEVTAB file */
	 || fstat(fd, &sbuf) < 0		/* fstat it */
	 || sbuf.st_size != Ndevtab		/* make sure it's the right size */
	 || sbuf.st_mtime < sbuf2.st_mtime	/* and more recent than DEVDIR */
	 || (devtab = mmap(0, Ndevtab, PROT_READ, MAP_FILE|MAP_SHARED, fd, 0))==(caddr_t) -1)
    {	/* one of the above failed, so don't mmap() the file */
	close(fd);		 	/* build DEVTAB from DEVDIR */
	if (dev_to_devtab()) {
	    devtab_initialized = 1;			 
	    return 1;
	} else
	    return 0;
    }
    devtab_initialized = 1;
    return 1;
}

/* 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. */

/* cannot update DEVTAB, malloc devtab instead */
#define ABORT_FILE_UPDATE	\
    close(fd);			\
    got_fd = 0;			\
    devtab = malloc(Ndevtab);	\
    goto devtab_allocated;

static int dev_to_devtab(void) {
    static struct stat sbuf;
    int fd, got_fd = 1, i, old_umask = umask(0);
    dev_t m;
    struct dirent* ent;
    DIR* dev;
    
    if (!(dev = opendir(DEVDIR))) {
	fprintf(stderr, "%s: %s\nCannot continue deducing device number -> name mapping.\n",
		DEVDIR, sys_errlist[errno]);
	umask(old_umask);
	return 0;
    }
    /* Try to get an fd open for write on DEVTAB in one of two possible ways */
    if ((fd = open(DEVTAB, O_RDWR|O_TRUNC)) < 0) {	/* try opening it first */ 
	unlink(DEVTAB);					/* if that fails unlink+open */
	if ((fd = open(DEVTAB, O_CREAT|O_RDWR|O_TRUNC, 0)) < 0)
	    { ABORT_FILE_UPDATE }
    }
    umask(old_umask);					/* not creating any more files */
    /* Set file to appropriate size, then mmap it. */
    if (lseek(fd, Ndevtab - 1, SEEK_SET) < 0)
	{ ABORT_FILE_UPDATE }
    if (write(fd, "", 1) < 0)
	{ ABORT_FILE_UPDATE }
    if ((devtab = mmap(0, Ndevtab, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, fd, 0))==(caddr_t)-1)
	{ ABORT_FILE_UPDATE }
devtab_allocated:
    memset((void*)devtab, 0, Ndevtab);	/* initialize devtab: necessary? */
    while ((ent = readdir(dev))) {	/* loop over all dirents in DEVDIR */
	if (lstat(name_to_path(ent->d_name), &sbuf) < 0) /* lstat entry (avoid symlinks) */
	    continue;			    /* failures do not interest us */
	if (!S_ISCHR(sbuf.st_mode))	    /* only support 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 (got_fd) close(fd);
    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 marked as 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.  An optional ~/.psdevtab may
be in order.  But really, how often does /dev change on typical systems?

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, we 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);
}

/* dev3 was really just the same as this except for the in-place q-mark. */
char *dev3(char *ttyname) {
    if (name_to_dev(ttyname) == (dev_t) -1) {
        strncpy(ttyname, " ? ", 3);	/* no ctty ==> question mark */
	return ttyname;
    } else				/* cvt to abbrev if a valid device */
	return abbrev_of_tty(ttyname);
}
