// 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 "types.h"
#include "user.h"
#include "fcntl.h"
#include "posix.h"
#include "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();
}