// SPDX-License-Identifier: GPL-2.0-or-later // // hwreport -- write down what this machine is, onto something you can carry. // // hwreport onto the first FAT volume mounted read-write, or the // first USB stick it can mount, or /root // hwreport into // // It exists for the machines that do not work. A laptop SchOS half-supports // has no network driver more often than not, and photos of a screen are a poor // way to carry a boot log and the tables that explain it. So this collects // what anybody debugging the port would ask for first -- the log, the PCI and // USB devices and what claimed each, the ACPI tables, the memory map, the // machine's name -- into one folder on a stick that opens on any other system. // // The layout is Linux's where Linux has one: the ACPI tables are the raw .dat // files `iasl -d` disassembles, and lspci.txt holds what `lspci -nnk -xxx` // would, so a report from here can be compared line for line with one taken // on Linux on the same machine. // // What it leaves out on purpose: serial numbers and the system UUID (the // kernel does not publish them), and the MSDM and SLIC tables, which hold a // Windows licence key. A report is meant to be handed to somebody. #include "core/types.h" #include "uapi/user.h" #include "uapi/fcntl.h" #include "uapi/posix.h" #include "uapi/stat.h" // --------------------------------------------------------------------------- // Small things a libc would have // --------------------------------------------------------------------------- static int slen(const char *s) { int n = 0; while (s[n]) n++; return n; } static int seq(const char *a, const char *b) { while (*a && *a == *b) { a++; b++; } return *a == *b; } static int sprefix(const char *s, const char *p) { while (*p && *s == *p) { s++; p++; } return *p == 0; } static void scpy(char *d, const char *s, int cap) { int n = 0; while (s[n] && n < cap - 1) { d[n] = s[n]; n++; } d[n] = 0; } // An output buffer, flushed to a file in one write -- each write to FAT is a // whole operation with its own flush, so fewer is much faster. struct buf { char *p; int n, cap; }; static char textmem[512 * 1024]; static struct buf T = { textmem, 0, sizeof textmem }; static void bc(struct buf *b, char c) { if (b->n < b->cap) b->p[b->n++] = c; } static void bs(struct buf *b, const char *s) { while (*s) bc(b, *s++); } static void bsn(struct buf *b, const char *s, int n) { while (n-- > 0) bc(b, *s++); } static void bu(struct buf *b, unsigned long long v) { char t[24]; int k = 0; do { t[k++] = '0' + v % 10; v /= 10; } while (v); while (k) bc(b, t[--k]); } static void bx(struct buf *b, unsigned long long v, int digits) { static const char h[] = "0123456789abcdef"; char t[16]; int k = 0; do { t[k++] = h[v & 0xF]; v >>= 4; } while (v || k < digits); while (k) bc(b, t[--k]); } static void bpad(struct buf *b, const char *s, int w) { int n = slen(s); bs(b, s); while (n++ < w) bc(b, ' '); } static void say(const char *s) { write(1, (void *)s, slen(s)); } static void saybuf(struct buf *b) { write(1, b->p, b->n); b->n = 0; } // --------------------------------------------------------------------------- // Files // --------------------------------------------------------------------------- static char iobuf[64 * 1024]; // The whole of a small file into `out`, terminated. Length, or -1. static int slurp(const char *path, char *out, int cap) { int fd = open((char *)path, O_RDONLY), n = 0, r; if (fd < 0) return -1; while (n < cap - 1 && (r = read(fd, out + n, cap - 1 - n)) > 0) n += r; close(fd); out[n] = 0; return n; } // One line of a file, trailing newline dropped; "" when it is missing. static char * line(const char *path) { static char l[8][256]; static int k; char *s = l[k++ & 7]; if (slurp(path, s, 256) < 0) s[0] = 0; for (int i = 0; s[i]; i++) if (s[i] == '\n') { s[i] = 0; break; } return s; } static char dir[512]; static int nfiles, nfail; static void path_in(char *out, const char *name) { int n = 0; for (const char *p = dir; *p && n < 500; p++) out[n++] = *p; out[n++] = '/'; for (const char *p = name; *p && n < 510; p++) out[n++] = *p; out[n] = 0; } static int put_file(const char *name, const char *data, int len) { char p[520]; path_in(p, name); int fd = open(p, O_WRONLY | O_CREATE | O_TRUNC); if (fd < 0) { nfail++; return -1; } int done = 0; while (done < len) { int r = write(fd, (void *)(data + done), len - done); if (r <= 0) break; done += r; } close(fd); if (done != len) { nfail++; return -1; } nfiles++; return 0; } static void put_text(const char *name) { put_file(name, T.p, T.n); T.n = 0; } // Stream `src` to `name`, for files of any size. static long copy_to(const char *src, const char *name) { char p[520]; int in = open((char *)src, O_RDONLY); if (in < 0) return -1; path_in(p, name); int fd = open(p, O_WRONLY | O_CREATE | O_TRUNC); if (fd < 0) { close(in); nfail++; return -1; } long total = 0; int r; while ((r = read(in, iobuf, sizeof iobuf)) > 0) { if (write(fd, iobuf, r) != r) { total = -1; break; } total += r; } close(in); close(fd); if (total < 0) nfail++; else nfiles++; return total; } // Every entry of a directory but the dots, sorted, into names[]. #define MAXNAMES 256 static char names[MAXNAMES][64]; static int list(const char *path) { static char dbuf[4096]; int fd = open((char *)path, O_RDONLY | O_DIRECTORY), n = 0, r; if (fd < 0) return 0; while ((r = getdents64(fd, dbuf, sizeof dbuf)) > 0) for (int off = 0; off < r; ) { struct dirent64 *d = (struct dirent64 *)(dbuf + off); off += d->d_reclen; if (seq(d->d_name, ".") || seq(d->d_name, "..") || n >= MAXNAMES) continue; scpy(names[n++], d->d_name, 64); } close(fd); for (int i = 1; i < n; i++) /* insertion sort; n is small */ for (int j = i; j > 0; j--) { const char *a = names[j - 1], *b = names[j]; while (*a && *a == *b) { a++; b++; } if ((unsigned char)*a <= (unsigned char)*b) break; char t[64]; scpy(t, names[j], 64); scpy(names[j], names[j - 1], 64); scpy(names[j - 1], t, 64); } return n; } static void join(char *out, const char *a, const char *b) { int n = 0; while (*a && n < 250) out[n++] = *a++; out[n++] = '/'; while (*b && n < 510) out[n++] = *b++; out[n] = 0; } // --------------------------------------------------------------------------- // Where to write // --------------------------------------------------------------------------- static int mounted_here; static const char *mnt = "/mnt/hwreport"; // The first read-write FAT mount in /proc/mounts. static int find_fat_mount(char *out, int cap) { static char m[4096]; if (slurp("/proc/mounts", m, sizeof m) < 0) return -1; for (char *l = m; *l; ) { char *f[4] = { 0 }; int nf = 0; char *e = l; while (*e && *e != '\n') e++; char save = *e; *e = 0; for (char *p = l; *p && nf < 4; ) { while (*p == ' ') *p++ = 0; if (!*p) break; f[nf++] = p; while (*p && *p != ' ') p++; } if (nf == 4 && seq(f[2], "vfat") && !sprefix(f[3], "ro")) { scpy(out, f[1], cap); return 0; } l = save ? e + 1 : e; } return -1; } // Mount the first USB disk or partition that takes FAT. Partitions first, // since a stick almost always has one and its whole-disk node is the table. static int mount_usb(char *out, int cap) { int n = list("/dev"); mkdir("/mnt"); mkdir((char *)mnt); for (int pass = 0; pass < 2; pass++) for (int i = 0; i < n; i++) { const char *nm = names[i]; if (!sprefix(nm, "usb")) continue; int haspart = 0; for (const char *p = nm + 3; *p; p++) if (*p == 'p') haspart = 1; if (haspart != (pass == 0)) continue; char dev[80]; join(dev, "/dev", nm); if (mount(dev, mnt, "vfat", 0, 0) == 0) { say("hwreport: mounted "); say(dev); say(" on "); say(mnt); say("\n"); mounted_here = 1; scpy(out, mnt, cap); return 0; } } return -1; } // "hwreport-20261023-142501", or "hwreport-N" when the clock is not set. static void make_dir(const char *base) { struct timespec ts; struct buf b = { dir, 0, sizeof dir - 1 }; bs(&b, base); if (b.n > 1 || base[0] != '/') bc(&b, '/'); bs(&b, "hwreport-"); if (clock_gettime(CLOCK_REALTIME, &ts) == 0 && ts.tv_sec > 946684800) { // Days to a civil date: Howard Hinnant's algorithm. long long s = ts.tv_sec, days = s / 86400, rem = s % 86400; long long z = days + 719468, era = z / 146097; long long doe = z - era * 146097; long long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; long long y = yoe + era * 400, doy = doe - (365 * yoe + yoe / 4 - yoe / 100); long long mp = (5 * doy + 2) / 153, d = doy - (153 * mp + 2) / 5 + 1; long long mo = mp < 10 ? mp + 3 : mp - 9; if (mo <= 2) y++; bu(&b, (unsigned long long)y); if (mo < 10) bc(&b, '0'); bu(&b, (unsigned long long)mo); if (d < 10) bc(&b, '0'); bu(&b, (unsigned long long)d); bc(&b, '-'); long long hh = rem / 3600, mm = rem / 60 % 60, ss = rem % 60; if (hh < 10) bc(&b, '0'); bu(&b, (unsigned long long)hh); if (mm < 10) bc(&b, '0'); bu(&b, (unsigned long long)mm); if (ss < 10) bc(&b, '0'); bu(&b, (unsigned long long)ss); dir[b.n] = 0; if (mkdir(dir) == 0) return; bc(&b, '-'); } int keep = b.n; for (int i = 1; i < 1000; i++) { b.n = keep; bu(&b, (unsigned long long)i); dir[b.n] = 0; if (mkdir(dir) == 0) return; } dir[0] = 0; } // --------------------------------------------------------------------------- // The report // --------------------------------------------------------------------------- static const char * chassis_name(int t) { static const char *const n[] = { 0, "Other", "Unknown", "Desktop", "Low Profile Desktop", "Pizza Box", "Mini Tower", "Tower", "Portable", "Laptop", "Notebook", "Hand Held", "Docking Station", "All in One", "Sub Notebook", "Space-saving", "Lunch Box", "Main Server Chassis", "Expansion Chassis", "SubChassis", "Bus Expansion Chassis", "Peripheral Chassis", "RAID Chassis", "Rack Mount Chassis", "Sealed-case PC", "Multi-system chassis", "Compact PCI", "AdvancedTCA", "Blade", "Blade Enclosure", "Tablet", "Convertible", "Detachable", "IoT Gateway", "Embedded PC", "Mini PC", "Stick PC", }; return t > 0 && t < (int)(sizeof n / sizeof n[0]) ? n[t] : "?"; } static int atoi_(const char *s) { int v = 0; while (*s >= '0' && *s <= '9') v = v * 10 + (*s++ - '0'); return v; } static unsigned hexv(const char *s) { unsigned v = 0; if (s[0] == '0' && s[1] == 'x') s += 2; for (;; s++) { int c = *s; if (c >= '0' && c <= '9') v = v * 16 + (unsigned)(c - '0'); else if (c >= 'a' && c <= 'f') v = v * 16 + (unsigned)(c - 'a' + 10); else if (c >= 'A' && c <= 'F') v = v * 16 + (unsigned)(c - 'A' + 10); else break; } return v; } // The value of KEY= in a uevent, or "". static const char * uevent_get(const char *ev, const char *key, char *out, int cap) { int kl = slen(key); out[0] = 0; for (const char *l = ev; *l; ) { if (sprefix(l, key) && l[kl] == '=') { int n = 0; for (const char *p = l + kl + 1; *p && *p != '\n' && n < cap - 1; p++) out[n++] = *p; out[n] = 0; break; } while (*l && *l != '\n') l++; if (*l) l++; } return out; } static struct buf S = { 0, 0, 0 }; // summary.txt, built alongside static char summem[64 * 1024]; static void dmi_section(void) { static const char *const f[] = { "sys_vendor", "product_name", "product_version", "product_family", "product_sku", "board_vendor", "board_name", "board_version", "bios_vendor", "bios_version", "bios_date", "bios_release", "chassis_vendor", "chassis_type", "chassis_version", }; char p[128]; for (int i = 0; i < (int)(sizeof f / sizeof f[0]); i++) { join(p, "/sys/class/dmi/id", f[i]); bpad(&T, f[i], 17); bs(&T, line(p)); bc(&T, '\n'); } put_text("dmi.txt"); char a[256], b[256]; scpy(a, line("/sys/class/dmi/id/sys_vendor"), 256); scpy(b, line("/sys/class/dmi/id/product_name"), 256); bs(&S, "Machine: "); bs(&S, a[0] || b[0] ? a : "(no SMBIOS table)"); bc(&S, ' '); bs(&S, b); const char *v = line("/sys/class/dmi/id/product_version"); if (v[0]) { bs(&S, " / "); bs(&S, v); } const char *fam = line("/sys/class/dmi/id/product_family"); if (fam[0]) { bs(&S, " ("); bs(&S, fam); bc(&S, ')'); } bs(&S, "\nBoard: "); bs(&S, line("/sys/class/dmi/id/board_vendor")); bc(&S, ' '); bs(&S, line("/sys/class/dmi/id/board_name")); bc(&S, ' '); bs(&S, line("/sys/class/dmi/id/board_version")); bs(&S, "\nFirmware: "); bs(&S, line("/sys/class/dmi/id/bios_vendor")); bc(&S, ' '); bs(&S, line("/sys/class/dmi/id/bios_version")); bc(&S, ' '); bs(&S, line("/sys/class/dmi/id/bios_date")); { struct stat st; bs(&S, stat("/sys/firmware/efi", &st) == 0 ? ", booted by UEFI" : ", booted by BIOS"); } const char *ct = line("/sys/class/dmi/id/chassis_type"); if (ct[0]) { bs(&S, "\nChassis: "); bs(&S, chassis_name(atoi_(ct))); bs(&S, " (type "); bs(&S, ct); bc(&S, ')'); } bc(&S, '\n'); } static void cpu_mem_section(void) { static char ci[16 * 1024]; int ncpu = 0; char model[128] = ""; if (slurp("/proc/cpuinfo", ci, sizeof ci) >= 0) for (char *l = ci; *l; ) { if (sprefix(l, "processor")) ncpu++; if (!model[0] && sprefix(l, "model name")) { char *p = l; while (*p && *p != ':') p++; if (*p) p++; while (*p == ' ') p++; int n = 0; while (p[n] && p[n] != '\n' && n < 127) { model[n] = p[n]; n++; } model[n] = 0; } while (*l && *l != '\n') l++; if (*l) l++; } bs(&S, "CPU: "); bs(&S, model[0] ? model : "?"); bs(&S, ", "); bu(&S, (unsigned long long)ncpu); bs(&S, ncpu == 1 ? " processor running\n" : " processors running\n"); bs(&S, "Memory: "); char *mt = line("/proc/meminfo"); while (*mt && *mt != ':') mt++; if (*mt) mt++; while (*mt == ' ') mt++; bs(&S, mt); bs(&S, " usable\nSchOS: "); bs(&S, line("/proc/version")); bc(&S, '\n'); } static const char * pci_class_name(unsigned cls) { switch (cls >> 8) { case 0x0100: return "SCSI"; case 0x0101: return "IDE"; case 0x0104: return "RAID"; case 0x0106: return "SATA"; case 0x0108: return "NVMe"; case 0x0200: return "Ethernet"; case 0x0280: return "Network (Wi-Fi?)"; case 0x0300: return "VGA"; case 0x0302: return "3D controller"; case 0x0380: return "Display"; case 0x0401: return "Audio"; case 0x0403: return "HD Audio"; case 0x0480: return "Multimedia"; case 0x0600: return "Host bridge"; case 0x0601: return "ISA bridge"; case 0x0604: return "PCI bridge"; case 0x0680: return "Bridge"; case 0x0805: return "SD host"; case 0x0880: return "System peripheral";case 0x0c03: return "USB"; case 0x0c05: return "SMBus"; case 0x0c80: return "Serial bus"; case 0x0d00: return "IrDA"; case 0x0d11: return "Bluetooth"; case 0x1180: return "Signal processing";case 0x0c09: return "CANbus"; case 0x0c10: return "Thunderbolt"; case 0x0780: return "Communication"; case 0x0700: return "Serial"; case 0x0c00: return "FireWire"; } switch (cls >> 16) { case 0x01: return "Storage"; case 0x02: return "Network"; case 0x03: return "Display"; case 0x04: return "Multimedia"; case 0x06: return "Bridge"; case 0x08: return "System"; case 0x0c: return "Serial bus"; } return "Other"; } static void pci_section(void) { static char ev[1024], cfg[256]; char p[256], q[256], drv[32], id[32]; int n = list("/sys/bus/pci/devices"), nodrv = 0; struct buf L = { 0, 0, 0 }; static char lmem[160 * 1024]; L.p = lmem; L.cap = sizeof lmem; // Copy the names: list() is reused below. static char devs[MAXNAMES][64]; for (int i = 0; i < n; i++) scpy(devs[i], names[i], 64); bs(&S, "\nPCI devices ("); bu(&S, (unsigned long long)n); bs(&S, "):\n"); for (int i = 0; i < n; i++) { join(p, "/sys/bus/pci/devices", devs[i]); join(q, p, "uevent"); if (slurp(q, ev, sizeof ev) < 0) continue; join(q, p, "class"); unsigned cls = hexv(line(q)); join(q, p, "revision"); unsigned rev = hexv(line(q)); uevent_get(ev, "DRIVER", drv, sizeof drv); uevent_get(ev, "PCI_ID", id, sizeof id); for (char *c = id; *c; c++) if (*c >= 'A' && *c <= 'F') *c += 'a' - 'A'; char sub[32]; uevent_get(ev, "PCI_SUBSYS_ID", sub, sizeof sub); for (char *c = sub; *c; c++) if (*c >= 'A' && *c <= 'F') *c += 'a' - 'A'; // lspci -nnk shape. const char *bdf = devs[i] + 5; bs(&L, bdf); bc(&L, ' '); bs(&L, pci_class_name(cls)); bs(&L, " ["); bx(&L, cls >> 8, 4); bs(&L, "]: ["); bs(&L, id); bc(&L, ']'); if (rev) { bs(&L, " (rev "); bx(&L, rev, 2); bc(&L, ')'); } bs(&L, "\n\tSubsystem: ["); bs(&L, sub); bs(&L, "]\n"); if (drv[0]) { bs(&L, "\tKernel driver in use: "); bs(&L, drv); bc(&L, '\n'); } join(q, p, "irq"); bs(&L, "\tIRQ: "); bs(&L, line(q)); bc(&L, '\n'); join(q, p, "config"); int cl = 0, fd = open(q, O_RDONLY); if (fd >= 0) { cl = read(fd, cfg, 256); close(fd); } for (int o = 0; o < cl; o += 16) { bx(&L, (unsigned)o, 2); bc(&L, ':'); for (int k = 0; k < 16 && o + k < cl; k++) { bc(&L, ' '); bx(&L, (unsigned char)cfg[o + k], 2); } bc(&L, '\n'); } bc(&L, '\n'); bs(&S, " "); bs(&S, bdf); bc(&S, ' '); bs(&S, id); bc(&S, ' '); bpad(&S, pci_class_name(cls), 18); if (drv[0]) { bs(&S, drv); } else { bs(&S, "-- no driver"); nodrv++; } bc(&S, '\n'); } if (n == 0) bs(&S, " (none found; is /sys mounted?)\n"); else if (nodrv) { bs(&S, " "); bu(&S, (unsigned long long)nodrv); bs(&S, " without a driver.\n"); } put_file("lspci.txt", L.p, L.n); } static void usb_section(void) { static char ev[1024]; char p[256], q[256], drv[32], prod[32]; int n = list("/sys/bus/usb/devices"); static char devs[MAXNAMES][64]; for (int i = 0; i < n; i++) scpy(devs[i], names[i], 64); bs(&S, "\nUSB devices ("); bu(&S, (unsigned long long)n); bs(&S, "):\n"); for (int i = 0; i < n; i++) { join(p, "/sys/bus/usb/devices", devs[i]); join(q, p, "uevent"); if (slurp(q, ev, sizeof ev) < 0) continue; uevent_get(ev, "DRIVER", drv, sizeof drv); uevent_get(ev, "PRODUCT", prod, sizeof prod); join(q, p, "idVendor"); const char *vid = line(q); join(q, p, "idProduct"); const char *pid = line(q); join(q, p, "speed"); const char *spd = line(q); bs(&T, devs[i]); bs(&T, ": ID "); bs(&T, vid); bc(&T, ':'); bs(&T, pid); bs(&T, ", "); bs(&T, spd); bs(&T, " Mb/s, driver "); bs(&T, drv[0] ? drv : "(none)"); bs(&T, "\n"); bsn(&T, ev, slen(ev)); bc(&T, '\n'); bs(&S, " "); bpad(&S, devs[i], 8); bs(&S, vid); bc(&S, ':'); bs(&S, pid); bs(&S, " "); bpad(&S, spd, 5); bs(&S, " Mb/s "); bs(&S, drv[0] ? drv : "-- no driver"); bc(&S, '\n'); } put_text("lsusb.txt"); } static void memmap_section(void) { char p[256], q[256]; int n = list("/sys/firmware/memmap"); static char ent[MAXNAMES][64]; unsigned long long ram = 0; for (int i = 0; i < n; i++) scpy(ent[i], names[i], 64); // Numerically, not as the names sort. for (int want = 0; want < n; want++) { char nm[16]; struct buf b = { nm, 0, 15 }; bu(&b, (unsigned long long)want); nm[b.n] = 0; join(p, "/sys/firmware/memmap", nm); join(q, p, "start"); char s[64]; scpy(s, line(q), 64); join(q, p, "end"); char e[64]; scpy(e, line(q), 64); join(q, p, "type"); const char *t = line(q); bpad(&T, s, 20); bpad(&T, e, 20); bs(&T, t); bc(&T, '\n'); if (seq(t, "System RAM")) { unsigned long long a = 0, z = 0; for (const char *c = s + 2; *c; c++) a = a * 16 + (unsigned long long)(*c <= '9' ? *c - '0' : (*c | 32) - 'a' + 10); for (const char *c = e + 2; *c; c++) z = z * 16 + (unsigned long long)(*c <= '9' ? *c - '0' : (*c | 32) - 'a' + 10); ram += z - a + 1; } } put_text("memmap.txt"); bs(&S, "Firmware memory map: "); bu(&S, (unsigned long long)n); bs(&S, " entries, "); bu(&S, ram >> 20); bs(&S, " MB of RAM\n"); } static void acpi_section(void) { char p[256], q[256]; static unsigned char hdr[36]; int n = list("/sys/firmware/acpi/tables"), skipped = 0, copied = 0; static char tabs[MAXNAMES][64]; for (int i = 0; i < n; i++) scpy(tabs[i], names[i], 64); mkdir((path_in(p, "acpi"), p)); bs(&T, "name length rev oem oem table oem rev\n"); for (int i = 0; i < n; i++) { join(p, "/sys/firmware/acpi/tables", tabs[i]); int fd = open(p, O_RDONLY), h = 0; if (fd >= 0) { h = read(fd, hdr, 36); close(fd); } // A Windows licence key, which belongs to the machine's owner. if (sprefix(tabs[i], "MSDM") || sprefix(tabs[i], "SLIC")) { bpad(&T, tabs[i], 9); bs(&T, "(left out: holds a Windows licence key)\n"); skipped++; continue; } if (h >= 36) { unsigned len = hdr[4] | hdr[5] << 8 | hdr[6] << 16 | (unsigned)hdr[7] << 24; bpad(&T, tabs[i], 9); char num[16]; struct buf b = { num, 0, 15 }; bu(&b, len); num[b.n] = 0; bpad(&T, num, 8); bu(&T, hdr[8]); bs(&T, hdr[8] < 10 ? " " : " "); bsn(&T, (char *)hdr + 10, 6); bs(&T, " "); bsn(&T, (char *)hdr + 16, 8); bs(&T, " 0x"); bx(&T, hdr[24] | hdr[25] << 8 | hdr[26] << 16 | (unsigned)hdr[27] << 24, 8); bc(&T, '\n'); } else { bpad(&T, tabs[i], 9); bs(&T, "(no header)\n"); } char dst[80]; struct buf b = { dst, 0, 70 }; bs(&b, "acpi/"); bs(&b, tabs[i]); bs(&b, ".dat"); dst[b.n] = 0; (void)q; if (copy_to(p, dst) >= 0) copied++; } put_text("acpi/tables.txt"); bs(&S, "ACPI tables: "); bu(&S, (unsigned long long)copied); bs(&S, " saved in acpi/"); if (skipped) { bs(&S, " ("); bu(&S, (unsigned long long)skipped); bs(&S, " left out: Windows licence key)"); } if (n == 0) bs(&S, " -- none found, so no ACPI at all"); bc(&S, '\n'); } static void proc_copies(void) { static const char *const f[] = { "cpuinfo", "meminfo", "interrupts", "devices", "partitions", "mounts", "pci", "netif", "version", "bootmode", "filesystems", "fsstat", "stat", }; char src[64], dst[64]; mkdir((path_in(dst, "proc"), dst)); for (int i = 0; i < (int)(sizeof f / sizeof f[0]); i++) { struct buf a = { src, 0, 60 }, b = { dst, 0, 60 }; bs(&a, "/proc/"); bs(&a, f[i]); src[a.n] = 0; bs(&b, "proc/"); bs(&b, f[i]); bs(&b, ".txt"); dst[b.n] = 0; copy_to(src, dst); } bs(&S, "\nDisks and partitions:\n"); static char pt[4096]; if (slurp("/proc/partitions", pt, sizeof pt) > 0) bs(&S, pt); } int main(int argc, char **argv) { char base[256]; S.p = summem; S.cap = sizeof summem; if (argc > 1 && (seq(argv[1], "-h") || seq(argv[1], "--help"))) { say("usage: hwreport [dir]\n" "Writes a folder describing this machine: the boot log, PCI and USB\n" "devices, ACPI tables, memory map and firmware identity. With no dir,\n" "onto a mounted FAT volume or the first USB stick that mounts.\n"); exit(); } if (argc > 1) scpy(base, argv[1], sizeof base); else if (find_fat_mount(base, sizeof base) != 0 && mount_usb(base, sizeof base) != 0) { say("hwreport: no FAT volume mounted and no USB stick would mount;\n" " writing to /root instead. Plug a FAT32 stick in and run\n" " it again, or give a directory: hwreport /mnt\n"); scpy(base, "/root", sizeof base); } make_dir(base); if (!dir[0]) { say("hwreport: cannot create a folder in "); say(base); say("\n"); exit(); } say("hwreport: writing "); say(dir); say("\n"); bs(&S, "SchOS hardware report\n=====================\n\n"); dmi_section(); cpu_mem_section(); memmap_section(); acpi_section(); pci_section(); usb_section(); proc_copies(); long kl = copy_to("/proc/kmsg", "dmesg.txt"); bs(&S, "\nFiles: summary.txt, dmesg.txt (the boot log"); if (kl >= 0) { bs(&S, ", "); bu(&S, (unsigned long long)kl); bs(&S, " bytes"); } bs(&S, "), dmi.txt, lspci.txt, lsusb.txt,\n" "memmap.txt, acpi/*.dat (iasl -d disassembles them), proc/*.txt\n"); put_file("summary.txt", S.p, S.n); // On the screen too, for a machine whose stick is about to be pulled. saybuf(&S); sync(); if (mounted_here) { if (umount2(mnt, 0) == 0) say("\nhwreport: unmounted; the stick can be pulled out.\n"); else say("\nhwreport: written and synced, but the unmount failed.\n"); } struct buf r = { iobuf, 0, 200 }; bs(&r, "hwreport: "); bu(&r, (unsigned long long)nfiles); bs(&r, " files in "); bs(&r, dir); if (nfail) { bs(&r, ", "); bu(&r, (unsigned long long)nfail); bs(&r, " could not be written"); } bs(&r, "\n"); saybuf(&r); exit(); }