[PATCH 3/3] devinfo: GNU/Linux device information helper Signed-off-by: Davidlohr Bueso <dave@xxxxxxx> --- misc-utils/devinfo.c | 1054 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 1054 insertions(+), 0 deletions(-) create mode 100644 misc-utils/devinfo.c diff --git a/misc-utils/devinfo.c b/misc-utils/devinfo.c new file mode 100644 index 0000000..30b428a --- /dev/null +++ b/misc-utils/devinfo.c @@ -0,0 +1,1054 @@ +/* + * devinfo - GNU/Linux device information helper + * + * Copyright (C) 2010 Davidlohr Bueso <dave@xxxxxxx> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdarg.h> + +#include <blkid.h> + +#include "tt.h" +#include "nls.h" + +/* procfs and sysfs paths */ +#define _PATH_PROC_DEVS "/proc/devices" +#define _PATH_SYS_DEV "/sys/dev" +#define _PATH_SYS_CHR _PATH_SYS_DEV "/char" +#define _PATH_SYS_BLK _PATH_SYS_DEV "/block" +#define _PATH_LEN_MAX strlen(_PATH_SYS_BLK) + 7 /* 5 = XXX:YYY */ + +#define MAX_MAJOR 4 /* up to three digits, plus an extra '\0' char*/ +#define MAX_MINOR MAX_MAJOR +#define BUFLEN 127 /* should be enough to read any ine in files for procfs and sysfs */ + +/* access (perms) code is heavily based on stat(1)'s implementation, from coreutils */ +#define S_ISCTG(p) 0 +#define S_ISDOOR(m) 0 +#define S_ISMPB(m) 0 +#define S_ISMPC(m) 0 +#define S_ISWHT(m) 0 +#define S_ISPORT(m) 0 +#define S_ISNWK(m) 0 +#define S_TYPEISTMO(p) 0 +#define S_ISOFD(p) 0 +#define S_ISOFL(p) 0 +#define IS_MIGRATED_FILE(statp) \ + (S_ISOFD (statp->st_dm_mode) || S_ISOFL (statp->st_dm_mode)) + +enum devtype { + DEV_BLK, + DEV_CHR, + DEV_UNKNOWN, +}; + +/* hold device specific information */ +struct devinfo { + dev_t devno; /* major, minor nums */ + int type; /* enum devtype */ + char *filename; /* obtained from cmdline */ + char *access; /* device permissions */ + char *devclass; /* ie: sound */ + char *devname; /* ie: video0 */ + char *driver; /* ie: uvcvideo */ + char *devtype; /* ie: usb_interface*/ + char *version; /* ie: 2.0.1 */ + char *name; /* ie: Integrated Camera */ + + /* block device specific info */ + int removable; /* ie: 0 */ + int ro; /* ie: 1 */ + char *vendor; /* ie: Generic */ + char *model; /* ie: Multi-Card */ +}; + +/* based on fdisk(8) */ +struct systypes { + unsigned char type; + char *name; +} sys_types[] = { + {0x00, N_("Empty")}, + {0x01, N_("FAT12")}, + {0x02, N_("XENIX root")}, + {0x03, N_("XENIX usr")}, + {0x04, N_("FAT16 <32M")}, + {0x05, N_("Extended")}, /* DOS 3.3+ extended partition */ + {0x06, N_("FAT16")}, /* DOS 16-bit >=32M */ + {0x07, N_("HPFS/NTFS")}, /* OS/2 IFS, eg, HPFS or NTFS or QNX */ + {0x08, N_("AIX")}, /* AIX boot (AIX -- PS/2 port) or SplitDrive */ + {0x09, N_("AIX bootable")}, /* AIX data or Coherent */ + {0x0a, N_("OS/2 Boot Manager")},/* OS/2 Boot Manager */ + {0x0b, N_("W95 FAT32")}, + {0x0c, N_("W95 FAT32 (LBA)")},/* LBA really is `Extended Int 13h' */ + {0x0e, N_("W95 FAT16 (LBA)")}, + {0x0f, N_("W95 Ext'd (LBA)")}, + {0x10, N_("OPUS")}, + {0x11, N_("Hidden FAT12")}, + {0x12, N_("Compaq diagnostics")}, + {0x14, N_("Hidden FAT16 <32M")}, + {0x16, N_("Hidden FAT16")}, + {0x17, N_("Hidden HPFS/NTFS")}, + {0x18, N_("AST SmartSleep")}, + {0x1b, N_("Hidden W95 FAT32")}, + {0x1c, N_("Hidden W95 FAT32 (LBA)")}, + {0x1e, N_("Hidden W95 FAT16 (LBA)")}, + {0x24, N_("NEC DOS")}, + {0x39, N_("Plan 9")}, + {0x3c, N_("PartitionMagic recovery")}, + {0x40, N_("Venix 80286")}, + {0x41, N_("PPC PReP Boot")}, + {0x42, N_("SFS")}, + {0x4d, N_("QNX4.x")}, + {0x4e, N_("QNX4.x 2nd part")}, + {0x4f, N_("QNX4.x 3rd part")}, + {0x50, N_("OnTrack DM")}, + {0x51, N_("OnTrack DM6 Aux1")}, /* (or Novell) */ + {0x52, N_("CP/M")}, /* CP/M or Microport SysV/AT */ + {0x53, N_("OnTrack DM6 Aux3")}, + {0x54, N_("OnTrackDM6")}, + {0x55, N_("EZ-Drive")}, + {0x56, N_("Golden Bow")}, + {0x5c, N_("Priam Edisk")}, + {0x61, N_("SpeedStor")}, + {0x63, N_("GNU HURD or SysV")}, /* GNU HURD or Mach or Sys V/386 (such as ISC UNIX) */ + {0x64, N_("Novell Netware 286")}, + {0x65, N_("Novell Netware 386")}, + {0x70, N_("DiskSecure Multi-Boot")}, + {0x75, N_("PC/IX")}, + {0x80, N_("Old Minix")}, /* Minix 1.4a and earlier */ + {0x81, N_("Minix / old Linux")},/* Minix 1.4b and later */ + {0x82, N_("Linux swap / Solaris")}, + {0x83, N_("Linux")}, + {0x84, N_("OS/2 hidden C: drive")}, + {0x85, N_("Linux extended")}, + {0x86, N_("NTFS volume set")}, + {0x87, N_("NTFS volume set")}, + {0x88, N_("Linux plaintext")}, + {0x8e, N_("Linux LVM")}, + {0x93, N_("Amoeba")}, + {0x94, N_("Amoeba BBT")}, /* (bad block table) */ + {0x9f, N_("BSD/OS")}, /* BSDI */ + {0xa0, N_("IBM Thinkpad hibernation")}, + {0xa5, N_("FreeBSD")}, /* various BSD flavours */ + {0xa6, N_("OpenBSD")}, + {0xa7, N_("NeXTSTEP")}, + {0xa8, N_("Darwin UFS")}, + {0xa9, N_("NetBSD")}, + {0xab, N_("Darwin boot")}, + {0xaf, N_("HFS / HFS+")}, + {0xb7, N_("BSDI fs")}, + {0xb8, N_("BSDI swap")}, + {0xbb, N_("Boot Wizard hidden")}, + {0xbe, N_("Solaris boot")}, + {0xbf, N_("Solaris")}, + {0xc1, N_("DRDOS/sec (FAT-12)")}, + {0xc4, N_("DRDOS/sec (FAT-16 < 32M)")}, + {0xc6, N_("DRDOS/sec (FAT-16)")}, + {0xc7, N_("Syrinx")}, + {0xda, N_("Non-FS data")}, + {0xdb, N_("CP/M / CTOS / ...")},/* CP/M or Concurrent CP/M or + Concurrent DOS or CTOS */ + {0xde, N_("Dell Utility")}, /* Dell PowerEdge Server utilities */ + {0xdf, N_("BootIt")}, /* BootIt EMBRM */ + {0xe1, N_("DOS access")}, /* DOS access or SpeedStor 12-bit FAT + extended partition */ + {0xe3, N_("DOS R/O")}, /* DOS R/O or SpeedStor */ + {0xe4, N_("SpeedStor")}, /* SpeedStor 16-bit FAT extended + partition < 1024 cyl. */ + {0xeb, N_("BeOS fs")}, + {0xee, N_("GPT")}, /* Intel EFI GUID Partition Table */ + {0xef, N_("EFI (FAT-12/16/32)")},/* Intel EFI System Partition */ + {0xf0, N_("Linux/PA-RISC boot")},/* Linux/PA-RISC boot loader */ + {0xf1, N_("SpeedStor")}, + {0xf4, N_("SpeedStor")}, /* SpeedStor large partition */ + {0xf2, N_("DOS secondary")}, /* DOS 3.3+ secondary */ + {0xfb, N_("VMware VMFS")}, + {0xfc, N_("VMware VMKCORE")}, /* VMware kernel dump partition */ + {0xfd, N_("Linux raid autodetect")},/* New (2.2.x) raid partition with + autodetect using persistent + superblock */ + {0xfe, N_("LANstep")}, /* SpeedStor >1024 cyl. or LANstep */ + {0xff, N_("BBT")}, /* Xenix Bad Block Table */ + { 0, 0 } +}; + +#define IS_BLK(s) s == DEV_BLK +#define IS_CHR(s) s == DEV_CHR +#define mksyspath(path,type,devid) \ + if (IS_CHR(type)) \ + sprintf(path, "%s/%d:%d", _PATH_SYS_CHR, major(devid), minor(devid)); \ + else if (IS_BLK(type)) \ + sprintf(path, "%s/%d:%d", _PATH_SYS_BLK, major(devid), minor(devid)); + + +/* + * exists: check if file exists + */ +static inline int exists(const char *file) +{ + struct stat filestat; + return stat(file, &filestat) == 0; +} + +/* + * check_prereqs: makes sure sysfs and procfs is mounted in the default locations + */ +static void check_prereqs(void) +{ + if (!exists(_PATH_PROC_DEVS)) + errx(EXIT_FAILURE, "error: must have procfs mounted in /proc"); + if (!exists(_PATH_SYS_DEV)) + errx(EXIT_FAILURE, "error: must have procfs mounted in /proc"); +} + +/* + * xmalloc: error handling malloc wrapper + */ +static inline void *xmalloc(const size_t size) +{ + void *ret = malloc(size); + if (!ret) + err(EXIT_FAILURE, _("could not allocate %d bytes of memory"), (int) size); + return ret; +} + +/* + * partition_type: return the name of a given partition type + */ +static char *partition_type(unsigned char type) +{ + int i; + + for (i = 0; i < sizeof(sys_types); i++) + if (type == sys_types[i].type) + return sys_types[i].name; +} + +/* Lookup a pattern and get the value, from lscpu(1). + * Format is: + * + * "<pattern>=<key>" + */ +static int lookup(char *line, char *pattern, char **value, char sep) +{ + char *p, *v; + int len = strlen(pattern); + + if (!*line) + return 0; + + /* pattern */ + if (strncmp(line, pattern, len)) + return 0; + + /* white spaces */ + for (p = line + len; isspace(*p); p++); + + /* separator */ + if (*p != sep) + return 0; + + /* white spaces */ + for (++p; isspace(*p); p++); + + /* value */ + if (!*p) + return 0; + v = p; + + /* end of value */ + len = strlen(line) - 1; + for (p = line + len; isspace(*(p-1)); p--); + *p = '\0'; + + *value = strdup(v); + return 1; +} + +/* from stat(1) */ +static char ftypelet(mode_t bits) +{ + /* These are the most common, so test for them first. */ + if (S_ISREG (bits)) + return '-'; + if (S_ISDIR (bits)) + return 'd'; + + /* Other letters standardized by POSIX 1003.1-2004. */ + if (S_ISBLK (bits)) + return 'b'; + if (S_ISCHR (bits)) + return 'c'; + if (S_ISLNK (bits)) + return 'l'; + if (S_ISFIFO (bits)) + return 'p'; + + /* Other file types (though not letters) standardized by POSIX. */ + if (S_ISSOCK (bits)) + return 's'; + + /* Nonstandard file types. */ + if (S_ISCTG (bits)) + return 'C'; + if (S_ISDOOR (bits)) + return 'D'; + if (S_ISMPB (bits) || S_ISMPC (bits)) + return 'm'; + if (S_ISNWK (bits)) + return 'n'; + if (S_ISPORT (bits)) + return 'P'; + if (S_ISWHT (bits)) + return 'w'; + + return '?'; +} + +void strmode (mode_t mode, char *str) +{ + str[0] = ftypelet (mode); + str[1] = mode & S_IRUSR ? 'r' : '-'; + str[2] = mode & S_IWUSR ? 'w' : '-'; + str[3] = (mode & S_ISUID + ? (mode & S_IXUSR ? 's' : 'S') + : (mode & S_IXUSR ? 'x' : '-')); + str[4] = mode & S_IRGRP ? 'r' : '-'; + str[5] = mode & S_IWGRP ? 'w' : '-'; + str[6] = (mode & S_ISGID + ? (mode & S_IXGRP ? 's' : 'S') + : (mode & S_IXGRP ? 'x' : '-')); + str[7] = mode & S_IROTH ? 'r' : '-'; + str[8] = mode & S_IWOTH ? 'w' : '-'; + str[9] = (mode & S_ISVTX + ? (mode & S_IXOTH ? 't' : 'T') + : (mode & S_IXOTH ? 'x' : '-')); + str[10] = ' '; + str[11] = '\0'; +} + +static void filemodestring(struct stat *statp, char *str) +{ + strmode(statp->st_mode, str); + + if (S_TYPEISSEM (statp)) + str[0] = 'F'; + else if (IS_MIGRATED_FILE (statp)) + str[0] = 'M'; + else if (S_TYPEISMQ (statp)) + str[0] = 'Q'; + else if (S_TYPEISSHM (statp)) + str[0] = 'S'; + else if (S_TYPEISTMO (statp)) + str[0] = 'T'; +} + + +static inline char *parse_oneliner(const char *path) +{ + FILE *fp; int i = 0; + char buf[127], *ret = NULL; + + if(!exists(path)) + goto ret; + + if (!(fp = fopen(path, "r"))) + goto ret; + + if(!fgets(buf, sizeof(buf), fp)) { + goto close; + } + + buf[strlen(buf) - 1] = '\0'; /* ciao newline */ + ret = malloc(sizeof(char) * strlen(buf)); + strcpy(ret, buf); +close: + fclose(fp); +ret: + return ret; +} + +/* + * read_basicinfo: get major/minor and device type information (block/char) from a given device. + */ +struct devinfo *read_basicinfo(char *device) +{ + struct stat sb; + struct devinfo *dev; + static char modebuf[12]; + char _type; + + if (!device) + return NULL; + + if (stat(device, &sb) == -1) + err(EXIT_FAILURE, _("error: `%s' is not a GNU/Linux device"), device); + if (S_ISCHR(sb.st_mode)) + _type = DEV_CHR; + else if (S_ISBLK(sb.st_mode)) + _type = DEV_BLK; + else + errx(EXIT_FAILURE, _("error: `%s' is not a GNU/Linux device"), device); + + dev = xmalloc(sizeof(struct devinfo)); + dev->type = _type; + dev->devno = sb.st_rdev; + dev->filename = device; + filemodestring(&sb, modebuf); + modebuf[10] = 0; + dev->access = modebuf; + + return dev; +} + +static void read_uevent(struct devinfo **_dev) +{ + char *str, path[BUFLEN], _path[BUFLEN]; + char buf[BUFLEN]; + struct devinfo *dev = *_dev; + FILE *fp; + + mksyspath(path, dev->type, dev->devno); + sprintf(_path, "%s/uevent", path); + if (exists(_path)) { + if (!(fp = fopen(_path, "r"))) return; + while (fgets(buf, sizeof(buf), fp)) { + if (lookup(buf, "DEVNAME", &dev->devname, '=')) ; + else if (lookup(buf, "DEVTYPE", &dev->devtype, '=')) ; + else if (lookup(buf, "DRIVER", &dev->driver, '=')) ; + else + continue; + } + fclose(fp); + } + + /* + * Sometimes some fields are not in uevent but present in + * /path/device/uevent, try to get it there + */ + sprintf(_path, "%s/device/uevent", path); + if (exists(_path)) { + if (!(fp = fopen(_path, "r"))) return; + while (fgets(buf, sizeof(buf), fp)) { + if (!dev->devname) + if (lookup(buf, "DEVNAME", &dev->devname, '=')) + continue; + if (!dev->devtype) + if (lookup(buf, "DEVTYPE", &dev->devtype, '=')) + continue; + + if (!dev->driver) + if (lookup(buf, "DRIVER", &dev->driver, '=')) + continue; + } + fclose(fp); + } +} + +/* + * read_file: try to get data from files containing only one line. + * filename ough to be version,name,vendor,model,etc. + */ +static char *read_file(char *filename, char path[]) +{ + char _path[BUFLEN], *ret = NULL; + + sprintf(_path, "%s/%s", path, filename); + ret = parse_oneliner(_path); + + if (!ret) { + /* + * so not it /sys/devices/{block/char}/{major:minor}/, + * try searching in /sys/devices/{block/char}/{major:minor}/device + */ + sprintf(_path, "%s/device/%s", path, filename); + ret = parse_oneliner(_path); + } + + return ret; + +} + +static void read_name(struct devinfo **_dev) +{ + char path[BUFLEN]; + + mksyspath(path, (*_dev)->type, (*_dev)->devno); + (*_dev)->name = read_file("name", path); +} + +/* + * read_version: reads /sys/dev/<type>/<major:minor>/version + * or /sys/dev/<type>/<major:minor>/device/version + */ +static void read_version(struct devinfo **_dev) +{ + char path[BUFLEN]; + + mksyspath(path, (*_dev)->type, (*_dev)->devno); + (*_dev)->version = read_file("version", path); +} + +/* + * read_removable: reads /sys/dev/<type>/<major:minor>/removable + * or /sys/dev/<type>/<major:minor>/device/removable + */ +static void read_removable(struct devinfo **_dev) +{ + char path[BUFLEN], *removable; + + mksyspath(path, (*_dev)->type, (*_dev)->devno); + sprintf(path, "%s/removable", path); + removable = parse_oneliner(path); + + if(!removable) { + sprintf(path, "%s/device/removable", path); + removable = parse_oneliner(path); + } + if (removable) + (*_dev)->removable = atoi(removable); + else (*_dev)->removable = -1; /* file does not exist! */ +} + +/* + * read_vendor: reads /sys/dev/<type>/<major:minor>/removable + * or /sys/dev/<type>/<major:minor>/device/removable + */ +static void read_vendor(struct devinfo **_dev) +{ + char path[BUFLEN]; + + mksyspath(path, (*_dev)->type, (*_dev)->devno); + (*_dev)->vendor = read_file("vendor", path); +} + +/* + * read_model: reads /sys/dev/<type>/<major:minor>/removable + * or /sys/dev/<type>/<major:minor>/device/removable + */ +static void read_model(struct devinfo **_dev) +{ + char path[BUFLEN]; + + mksyspath(path, (*_dev)->type, (*_dev)->devno); + (*_dev)->model = read_file("model", path); +} + +/* + * read_ro: reads /sys/dev/<type>/<major:minor>/ro + * or /sys/dev/<type>/<major:minor>/device/ro + */ +static void read_ro(struct devinfo **_dev) +{ + char *tmp, path[BUFLEN]; + + mksyspath(path, (*_dev)->type, (*_dev)->devno); + tmp = read_file("ro", path); + + if (tmp) + (*_dev)->ro = atoi(tmp); + else /* dfiles does not exist */ + (*_dev)->ro = -1; + free(tmp); +} + +/* + * get_devclass: obtains the major device class name, based on the major number + */ +static char *get_devclass(int imajor, int type) +{ + int foundblk = 0; + char *devclass = NULL, *aux, *tmp, major[MAX_MAJOR], buf[BUFLEN]; + FILE *fp = fopen(_PATH_PROC_DEVS, "r"); + + if (!fp) + return NULL; + +#define CHR_DELIM "Character devices:" +#define BLK_DELIM "Block devices:" + + /* kernel prints the major number from 3 spaces, hence %3d */ + sprintf(major, "%3d", imajor); + + switch (type) { + case DEV_CHR: + while (fgets(buf, sizeof(buf), fp)) { + /* make sure we are searching for char devs */ + if (strncmp(buf, CHR_DELIM, strlen(CHR_DELIM)) == 0) + continue; + /* search for the major number under char devs */ + if (strncmp(buf, major, strlen(major)) == 0) { + /* found it, now get out of here! */ + tmp = strtok_r(buf, " ", &aux); + devclass = strdup(aux); + break; + } + /* done reading char info, not interested in block devs */ + else if (strncmp(buf, BLK_DELIM, strlen(BLK_DELIM)) == 0 || *buf == '\n') + break; + } + break; + case DEV_BLK: + while (fgets(buf, sizeof(buf), fp)) { + if (strncmp(buf, BLK_DELIM, strlen(BLK_DELIM)) == 0 || *buf == '\n') { + foundblk = 1; + continue; + } + /* now that we are sure we are reading block devs, lets search for the major number */ + if (foundblk) { + if (strncmp(buf, major, strlen(major)) == 0) { + /* found it, now get out of here! */ + tmp = strtok_r(buf, " ", &aux); + devclass = strdup(aux); + break; + } + } + } + break; + } + + fclose(fp); + if (devclass) /* it's actually normal to return NULL sometimes */ + devclass[strlen(devclass) - 1] = '\0'; + return devclass; +} + +/* + * free_devinfo: free all used memory + */ +static void free_devinfo(struct devinfo *dev) +{ + if (dev->devtype) free(dev->devtype); + if (dev->devclass) free(dev->devclass); + if (dev->devname) free(dev->devname); + if (dev->driver) free(dev->driver); + if (dev->name) free(dev->name); + if (dev->version) free(dev->version); + if (dev->vendor) free(dev->vendor); + if (dev->model) free(dev->model); + free(dev); +} + +/* + * print_blkdev: print block device specific information + */ +static void print_blkdev(struct devinfo *dev) +{ + int i, nparts; + blkid_probe pr; + blkid_partlist ls; + + if (pr = blkid_new_probe_from_filename(dev->filename)) { + if (strncmp(dev->devtype, "disk", strlen("disk")) == 0) { + if (ls = blkid_probe_get_partitions(pr)) { + nparts = blkid_partlist_numof_partitions(ls); + if (nparts) { + printf("\n Partitions (%d):", blkid_partlist_numof_partitions(ls)); + for (i = 0; i < nparts; i++) { + const char *p; + blkid_parttable root_tab; + blkid_partition par = blkid_partlist_get_partition(ls, i); + + printf("\n #%d: %10llu %s", + blkid_partition_get_partno(par), + (unsigned long long) blkid_partition_get_size(par), + partition_type(blkid_partition_get_type(par))); + } + } + } + } + + else if (strncmp(dev->devtype, "partition", strlen("partition")) == 0) { + /* enable topology probing */ + blkid_probe_enable_superblocks(pr, 1); + + /* set all flags */ + blkid_probe_set_superblocks_flags(pr, + BLKID_SUBLKS_LABEL | BLKID_SUBLKS_LABELRAW | + BLKID_SUBLKS_UUID | BLKID_SUBLKS_UUIDRAW | + BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE | + BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION | + BLKID_SUBLKS_MAGIC); + + int rc = blkid_do_safeprobe(pr); + if (!rc) { + int i, nvals = blkid_probe_numof_values(pr); + + for (i = 0; i < nvals; i++) { + const char *name, *data; + + blkid_probe_get_value(pr, i, &name, &data, NULL); + if (strncmp(name, "TYPE", strlen("TYPE")) == 0) + printf(", %s", data); + else if (strncmp(name, "USAGE", strlen("USAGE")) == 0) + printf(" %s", data); + } + } + } + blkid_free_probe(pr); + } +} + +/* + * print_spaces: print n-m spaces to stdout. Used for tabulating the output. + */ +static void print_spaces(int devtype, size_t m) +{ + size_t n = 0; + + /* + * Since one of the strings below will be the longest, + * use these as the base. + */ + if (IS_CHR(devtype)) + n = strlen("Character device"); + else if (IS_BLK(devtype)) + n = strlen("Block device"); + n -= m; + while (n--) + printf(" "); +} + +static void print_basic(struct devinfo *dev) +{ + /* + * we should always know: + * device name, char/block, major:minor and device class (major name) + */ + if (IS_CHR(dev->type)) + printf("Character device: "); + else if (IS_BLK(dev->type)) + printf("Block device: "); + printf("`%s' (%d:%d)", dev->devname ? dev->devname:dev->filename, + major(dev->devno), minor(dev->devno)); + + if (dev->devclass) printf(" Class: %s", dev->devclass); + if (dev->access) printf(" Access: %s", dev->access); +} + +static int print_name(struct devinfo *dev) +{ + int ret = 0; + + if (dev->name) { + print_spaces(dev->type, strlen("Name")); + printf("Name: %s", dev->name); + + if (dev->vendor) + printf(" Frabricant: %s %s", dev->vendor, dev->model); + + if (dev->driver) { + printf(" Driver: `%s'", dev->driver); + if (dev->version) /* no point printing version if we don't know the driver */ + printf("(version: %s)", dev->version); + } + ret = 1; + } + + else if (dev->vendor) { + print_spaces(dev->type, strlen("Fabricant")); + printf("%s %s %s", "Fabricant: ", dev->vendor, dev->model); + if (dev->driver) { + printf(" Driver: `%s'", dev->driver); + if (dev->version) + printf("(version: %s)", dev->version); + } + ret = 1; + } + + else if (dev->driver) { + printf("Driver: `%s'", dev->driver); + if (dev->version) + printf("(version: %s)", dev->version); + ret = 1; + } + + return ret; +} + +static int print_type(struct devinfo *dev) +{ + int ret = 0; + + if (dev->devtype) { + print_spaces(dev->type, strlen("Type")); + printf("Type: %s", dev->devtype); + + if (dev->ro != -1 && dev->removable != -1) { + /* yay! we get to print all */ + if (dev->ro) + printf(" (read-only, "); + else + printf(" (read-write, "); + if (dev->removable) + printf("removable)"); + else + printf("non-removable)"); + } + else { + if (dev->ro != -1) { + if (dev->ro) + printf(" (read-only)"); + else + printf(" (read-write)"); + } + else if (dev->removable != -1) { + if (dev->removable) + printf(" (removable)"); + else + printf(" (non-removable)"); + } + } + if (IS_BLK(dev->type)) + print_blkdev(dev); + ret = 1; + } + + return ret; +} + +/* + * print_device: print device information to stdout. Do not care about the device type + */ +static void print_device(struct devinfo *dev) +{ + assert(dev); + + print_basic(dev); + printf("\n"); + if (print_name(dev)) printf("\n"); + if (print_type(dev)) printf("\n"); +} + +/* + * filecmp: compare two file names, to be used with qsort(3) + */ +static int filecmp(const void *a, const void *b) +{ + char *v1 = *(char **) a; + char *v2 = *(char **) b; + + return strcmp(v1, v2); +} + + +/* + * get_devname: read the uevent file and return the value of DEVNAME + * This entry should always exists, so it would be weird to return null + */ +static char *get_devname(int type, int major, int minor) +{ + char buf[BUFLEN], path[BUFLEN], *devname = NULL; + FILE *fp; + + if (IS_CHR(type)) + sprintf(path, "%s/%d:%d/uevent", _PATH_SYS_CHR, major, minor); + else if (IS_BLK(type)) + sprintf(path, "%s/%d:%d/uevent", _PATH_SYS_BLK, major, minor); + + if (!(fp = fopen(path, "r"))) + goto ret; + while (fgets(buf, sizeof(buf), fp)) { + if (lookup(buf, "DEVNAME", &devname, '=')) + break; + else + continue; + } + fclose(fp); +ret: + return devname; +} + +/* + * numfiles: return the amount of files in char or block sysfs directories, depending on type + */ +static int numfiles(int type) +{ + int n = 0; + DIR *dir = NULL; + struct dirent *de; + + if (IS_CHR(type)) { + if (!(dir = opendir(_PATH_SYS_CHR))) + goto ret; + } + else if(IS_BLK(type)) { + if (!(dir = opendir(_PATH_SYS_BLK))) + goto ret; + } + else + goto ret; + + for (; (de = readdir(dir)); n++) ; + n -= 2; /* ommit '.' and '..' */ + + closedir(dir); +ret: + return n; +} + +/* + * list_devs: print list of char/blk devices to stdout, called with -c/-b options, respectively + */ +enum { DEVCOL, NUMCOL }; + +static void list_devs(int type) +{ + char *sminor, *devnums; + int flags = 0, i=0, major, minor, being_used = -1; + struct dirent *de; + DIR *dir = NULL; + char *files[numfiles(type)]; + struct tt *tb = NULL; + struct tt_line *ln = NULL, *root = NULL; + + if (IS_CHR(type)) { + if (!(dir = opendir(_PATH_SYS_CHR))) + return; + } + else if(IS_BLK(type)) { + if (!(dir = opendir(_PATH_SYS_BLK))) + return; + } + + while ((de = readdir(dir))) { + if(strcmp(de->d_name, ".") == 0 || + strcmp(de->d_name, "..") == 0) + continue; + + /* store names in array, for later sorting */ + files[i++] = strdup(de->d_name); + } + files[i] = NULL; + closedir(dir); + qsort(files, i, sizeof(files[0]), filecmp); + + tb = tt_new_table(flags); + if (!tb) + err(EXIT_FAILURE, "table initialization failed"); + + tt_define_column(tb, "DEVICE", 0.5, TT_FL_TREE); + tt_define_column(tb, "MAJOR:MINOR", 0.5, 0); + + for (i = 0; files[i]; i++) { + major = atoi(strtok_r(files[i], ":", &sminor)); + minor = atoi(sminor); + + if (being_used != major) { + /* found a dev with a new major number */ + being_used = major; + root = ln = tt_add_line(tb, NULL); + tt_line_set_data(ln, DEVCOL, get_devclass(major, type)); + } + ln = tt_add_line(tb, root); + tt_line_set_data(ln, DEVCOL, get_devname(type, major, minor)); + if (asprintf(&devnums, "%d:%d", being_used, minor)) + tt_line_set_data(ln, NUMCOL, devnums); + } + + tt_print_table(tb); + + tt_free_table(tb); + for (i=0; files[i]; i++) + free(files[i]); +} + +static void usage(int rc) +{ + printf(_("Usage: %s [option]\n"), + program_invocation_short_name); + + puts(_( "GNU/Linux device information helper\n\n" + " -d, --device <devicename> print information about the device" + "\n\t\t\t (ommitable)\n" + " -b, --block list block devices\n" + " -c, --char list character devices\n" + " -h, --help usage information\n")); + exit(rc); +} + +int main(int argc, char *argv[]) +{ + int c; + char *device_name = NULL; /* passed from input */ + struct devinfo *dev = NULL; + struct option longopts[] = { + { "device", required_argument, 0, 'd' }, + { "block", no_argument, 0, 'b' }, + { "char", no_argument, 0, 'c' }, + { "help", no_argument, 0, 'h' }, + { NULL, 0, 0, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + while ((c = getopt_long(argc, argv, "d:cbh", longopts, NULL)) != -1) { + switch (c) { + case 'd': + device_name = optarg; + break; + case 'c': + list_devs(DEV_CHR); + exit(EXIT_SUCCESS); + case 'b': + list_devs(DEV_BLK); + exit(EXIT_SUCCESS); + case 'h': + usage(EXIT_SUCCESS); + default: + usage(EXIT_FAILURE); + } + } + + /* make sure program is runnable */ + if (argc < 2) + usage(EXIT_FAILURE); + check_prereqs(); + if (!device_name) /* no -d option passed */ + device_name = argv[1]; + + /* first try to get the major/minor nums and dev type */ + dev = read_basicinfo(device_name); + /* now get the rest, if available */ + read_uevent(&dev); + read_name(&dev); + read_version(&dev); + read_removable(&dev); + read_vendor(&dev); + read_model(&dev); + read_ro(&dev); + dev->devclass = get_devclass(major(dev->devno), dev->type); + + print_device(dev); + + free_devinfo(dev); + return EXIT_SUCCESS; +} -- 1.7.0.4 -- To unsubscribe from this list: send the line "unsubscribe util-linux-ng" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html