| /* Copyright (C) 2004,2005,2006 Andi Kleen, SuSE Labs. |
| Copyright (C) 2008 Intel Corporation |
| Authors: Andi Kleen, Ying Huang |
| Decode IA32/x86-64 machine check events in /dev/mcelog. |
| |
| mcelog 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; version |
| 2. |
| |
| mcelog 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 find a copy of v2 of the GNU General Public License somewhere |
| on your Linux system; if not, write to the Free Software Foundation, |
| Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ |
| |
| #define _GNU_SOURCE 1 |
| #include <sys/fcntl.h> |
| #include <sys/ioctl.h> |
| #include <asm/types.h> |
| #include <asm/ioctls.h> |
| #include <linux/limits.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <syslog.h> |
| #include <ctype.h> |
| #include <poll.h> |
| #include <time.h> |
| #include <getopt.h> |
| #include <errno.h> |
| #include <stddef.h> |
| #include <assert.h> |
| #include <signal.h> |
| #include <pwd.h> |
| #include <fnmatch.h> |
| #include "mcelog.h" |
| #include "paths.h" |
| #include "k8.h" |
| #include "intel.h" |
| #include "p4.h" |
| #include "dmi.h" |
| #include "dimm.h" |
| #include "tsc.h" |
| #include "version.h" |
| #include "config.h" |
| #include "memutil.h" |
| #include "eventloop.h" |
| #include "memdb.h" |
| #include "server.h" |
| #include "trigger.h" |
| #include "client.h" |
| #include "msg.h" |
| #include "yellow.h" |
| #include "page.h" |
| #include "bus.h" |
| #include "unknown.h" |
| |
| enum cputype cputype = CPU_GENERIC; |
| |
| char *logfn = LOG_DEV_FILENAME; |
| |
| int ignore_nodev; |
| int filter_bogus = 1; |
| int cpu_forced; |
| static double cpumhz; |
| static int cpumhz_forced; |
| int ascii_mode; |
| int dump_raw_ascii; |
| int daemon_mode; |
| static char *inputfile; |
| char *processor_flags; |
| static int foreground; |
| int filter_memory_errors; |
| static struct config_cred runcred = { .uid = -1U, .gid = -1U }; |
| static int numerrors; |
| static char pidfile_default[] = PID_FILE; |
| static char logfile_default[] = LOG_FILE; |
| static char *pidfile = pidfile_default; |
| static char *logfile; |
| static int debug_numerrors; |
| int imc_log = -1; |
| static int check_only = 0; |
| |
| static int is_cpu_supported(void); |
| |
| |
| static void disclaimer(void) |
| { |
| Wprintf("Hardware event. This is not a software error.\n"); |
| } |
| |
| static char *extended_bankname(unsigned bank) |
| { |
| static char buf[64]; |
| switch (bank) { |
| case MCE_THERMAL_BANK: |
| return "THERMAL EVENT"; |
| case MCE_TIMEOUT_BANK: |
| return "Timeout waiting for exception on other CPUs"; |
| case K8_MCE_THRESHOLD_BASE ... K8_MCE_THRESHOLD_TOP: |
| return k8_bank_name(bank); |
| |
| /* add more extended banks here */ |
| |
| default: |
| sprintf(buf, "Undecoded extended event %x", bank); |
| return buf; |
| } |
| } |
| |
| static char *bankname(unsigned bank) |
| { |
| static char numeric[64]; |
| if (bank >= MCE_EXTENDED_BANK) |
| return extended_bankname(bank); |
| |
| switch (cputype) { |
| case CPU_K8: |
| return k8_bank_name(bank); |
| CASE_INTEL_CPUS: |
| return intel_bank_name(bank); |
| /* add banks of other cpu types here */ |
| default: |
| sprintf(numeric, "BANK %d", bank); |
| return numeric; |
| } |
| } |
| |
| static void resolveaddr(unsigned long long addr) |
| { |
| if (addr && do_dmi && dmi_forced) |
| dmi_decodeaddr(addr); |
| /* Should check for PCI resources here too */ |
| } |
| |
| static int mce_filter(struct mce *m, unsigned recordlen) |
| { |
| if (!filter_bogus) |
| return 1; |
| /* Filter out known broken MCEs */ |
| switch (cputype) { |
| case CPU_K8: |
| return mce_filter_k8(m); |
| /* add more buggy CPUs here */ |
| CASE_INTEL_CPUS: |
| return mce_filter_intel(m, recordlen); |
| default: |
| case CPU_GENERIC: |
| return 1; |
| } |
| } |
| |
| static void print_tsc(int cpunum, __u64 tsc, unsigned long time) |
| { |
| int ret = -1; |
| char *buf = NULL; |
| |
| if (cpumhz_forced) |
| ret = decode_tsc_forced(&buf, cpumhz, tsc); |
| else if (!time) |
| ret = decode_tsc_current(&buf, cpunum, cputype, cpumhz, tsc); |
| Wprintf("TSC %llx %s", tsc, ret >= 0 && buf ? buf : ""); |
| free(buf); |
| } |
| |
| struct cpuid1 { |
| unsigned stepping : 4; |
| unsigned model : 4; |
| unsigned family : 4; |
| unsigned type : 2; |
| unsigned res1 : 2; |
| unsigned ext_model : 4; |
| unsigned ext_family : 8; |
| unsigned res2 : 4; |
| }; |
| |
| static void parse_cpuid(u32 cpuid, u32 *family, u32 *model) |
| { |
| union { |
| struct cpuid1 c; |
| u32 v; |
| } c; |
| |
| /* Algorithm from IA32 SDM 2a 3-191 */ |
| c.v = cpuid; |
| *family = c.c.family; |
| if (*family == 0xf) |
| *family += c.c.ext_family; |
| *model = c.c.model; |
| if (*family == 6 || *family == 0xf) |
| *model += c.c.ext_model << 4; |
| } |
| |
| static u32 unparse_cpuid(unsigned family, unsigned model) |
| { |
| union { |
| struct cpuid1 c; |
| u32 v; |
| } c; |
| |
| c.c.family = family; |
| if (family >= 0xf) { |
| c.c.family = 0xf; |
| c.c.ext_family = family - 0xf; |
| } |
| c.c.model = model & 0xf; |
| if (family == 6 || family == 0xf) |
| c.c.ext_model = model >> 4; |
| return c.v; |
| } |
| |
| static char *cputype_name[] = { |
| [CPU_GENERIC] = "generic CPU", |
| [CPU_P6OLD] = "Intel PPro/P2/P3/old Xeon", |
| [CPU_CORE2] = "Intel Core", /* 65nm and 45nm */ |
| [CPU_K8] = "AMD K8 and derivates", |
| [CPU_P4] = "Intel P4", |
| [CPU_NEHALEM] = "Intel Xeon 5500 series / Core i3/5/7 (\"Nehalem/Westmere\")", |
| [CPU_DUNNINGTON] = "Intel Xeon 7400 series", |
| [CPU_TULSA] = "Intel Xeon 7100 series", |
| [CPU_INTEL] = "Intel generic architectural MCA", |
| [CPU_XEON75XX] = "Intel Xeon 7500 series", |
| [CPU_SANDY_BRIDGE] = "Sandy Bridge", /* Fill in better name */ |
| [CPU_SANDY_BRIDGE_EP] = "Sandy Bridge EP", /* Fill in better name */ |
| [CPU_IVY_BRIDGE] = "Ivy Bridge", /* Fill in better name */ |
| [CPU_IVY_BRIDGE_EPEX] = "Intel Xeon v2 (Ivy Bridge) EP/EX", /* Fill in better name */ |
| [CPU_HASWELL] = "Haswell", /* Fill in better name */ |
| [CPU_HASWELL_EPEX] = "Intel Xeon v3 (Haswell) EP/EX", |
| [CPU_BROADWELL] = "Broadwell", |
| [CPU_BROADWELL_DE] = "Intel Xeon (Broadwell) D family", |
| [CPU_BROADWELL_EPEX] = "Intel Xeon v4 (Broadwell) EP/EX", |
| [CPU_KNIGHTS_LANDING] = "Knights Landing", |
| [CPU_KNIGHTS_MILL] = "Knights Mill", |
| [CPU_ATOM] = "ATOM", |
| [CPU_SKYLAKE] = "Skylake", |
| [CPU_SKYLAKE_XEON] = "Skylake server", |
| [CPU_KABYLAKE] = "Kabylake", |
| [CPU_DENVERTON] = "Denverton", |
| }; |
| |
| static struct config_choice cpu_choices[] = { |
| { "generic", CPU_GENERIC }, |
| { "p6old", CPU_P6OLD }, |
| { "core2", CPU_CORE2 }, |
| { "k8", CPU_K8 }, |
| { "p4", CPU_P4 }, |
| { "dunnington", CPU_DUNNINGTON }, |
| { "xeon74xx", CPU_DUNNINGTON }, |
| { "xeon7400", CPU_DUNNINGTON }, |
| { "xeon5500", CPU_NEHALEM }, |
| { "xeon5200", CPU_CORE2 }, |
| { "xeon5000", CPU_P4 }, |
| { "xeon5100", CPU_CORE2 }, |
| { "xeon3100", CPU_CORE2 }, |
| { "xeon3200", CPU_CORE2 }, |
| { "core_i7", CPU_NEHALEM }, |
| { "core_i5", CPU_NEHALEM }, |
| { "core_i3", CPU_NEHALEM }, |
| { "nehalem", CPU_NEHALEM }, |
| { "westmere", CPU_NEHALEM }, |
| { "xeon71xx", CPU_TULSA }, |
| { "xeon7100", CPU_TULSA }, |
| { "tulsa", CPU_TULSA }, |
| { "intel", CPU_INTEL }, |
| { "xeon75xx", CPU_XEON75XX }, |
| { "xeon7500", CPU_XEON75XX }, |
| { "xeon7200", CPU_CORE2 }, |
| { "xeon7100", CPU_P4 }, |
| { "sandybridge", CPU_SANDY_BRIDGE }, /* Fill in better name */ |
| { "sandybridge-ep", CPU_SANDY_BRIDGE_EP }, /* Fill in better name */ |
| { "ivybridge", CPU_IVY_BRIDGE }, /* Fill in better name */ |
| { "ivybridge-ep", CPU_IVY_BRIDGE_EPEX }, |
| { "ivybridge-ex", CPU_IVY_BRIDGE_EPEX }, |
| { "haswell", CPU_HASWELL }, /* Fill in better name */ |
| { "haswell-ep", CPU_HASWELL_EPEX }, |
| { "haswell-ex", CPU_HASWELL_EPEX }, |
| { "broadwell", CPU_BROADWELL }, |
| { "broadwell-d", CPU_BROADWELL_DE }, |
| { "broadwell-ep", CPU_BROADWELL_EPEX }, |
| { "broadwell-ex", CPU_BROADWELL_EPEX }, |
| { "knightslanding", CPU_KNIGHTS_LANDING }, |
| { "knightsmill", CPU_KNIGHTS_MILL }, |
| { "xeon-v2", CPU_IVY_BRIDGE_EPEX }, |
| { "xeon-v3", CPU_HASWELL_EPEX }, |
| { "xeon-v4", CPU_BROADWELL_EPEX }, |
| { "atom", CPU_ATOM }, |
| { "skylake", CPU_SKYLAKE }, |
| { "skylake_server", CPU_SKYLAKE_XEON }, |
| { "kabylake", CPU_KABYLAKE }, |
| { "denverton", CPU_DENVERTON }, |
| { NULL } |
| }; |
| |
| static void print_cputypes(void) |
| { |
| struct config_choice *c; |
| fprintf(stderr, "Valid CPUs:"); |
| for (c = cpu_choices; c->name; c++) |
| fprintf(stderr, " %s", c->name); |
| fputc('\n', stderr); |
| } |
| |
| static enum cputype lookup_cputype(char *name) |
| { |
| struct config_choice *c; |
| for (c = cpu_choices; c->name; c++) { |
| if (!strcasecmp(name, c->name)) |
| return c->val; |
| } |
| fprintf(stderr, "Unknown CPU type `%s' specified\n", name); |
| print_cputypes(); |
| exit(1); |
| } |
| |
| static char *vendor[] = { |
| [0] = "Intel", |
| [1] = "Cyrix", |
| [2] = "AMD", |
| [3] = "UMC", |
| [4] = "vendor 4", |
| [5] = "Centaur", |
| [6] = "vendor 6", |
| [7] = "Transmeta", |
| [8] = "NSC" |
| }; |
| |
| static unsigned cpuvendor_to_num(char *name) |
| { |
| unsigned i; |
| unsigned v; |
| char *end; |
| |
| v = strtoul(name, &end, 0); |
| if (end > name) |
| return v; |
| for (i = 0; i < NELE(vendor); i++) |
| if (!strcmp(name, vendor[i])) |
| return i; |
| return 0; |
| } |
| |
| static char *cpuvendor_name(u32 cpuvendor) |
| { |
| return (cpuvendor < NELE(vendor)) ? vendor[cpuvendor] : "Unknown vendor"; |
| } |
| |
| static enum cputype setup_cpuid(u32 cpuvendor, u32 cpuid) |
| { |
| u32 family, model; |
| |
| parse_cpuid(cpuid, &family, &model); |
| |
| switch (cpuvendor) { |
| case X86_VENDOR_INTEL: |
| return select_intel_cputype(family, model); |
| case X86_VENDOR_AMD: |
| if (family >= 15 && family <= 17) |
| return CPU_K8; |
| /* FALL THROUGH */ |
| default: |
| Eprintf("Unknown CPU type vendor %u family %u model %u", |
| cpuvendor, family, model); |
| return CPU_GENERIC; |
| } |
| } |
| |
| static void mce_cpuid(struct mce *m) |
| { |
| static int warned; |
| if (m->cpuid) { |
| enum cputype t = setup_cpuid(m->cpuvendor, m->cpuid); |
| if (!cpu_forced) |
| cputype = t; |
| else if (t != cputype && t != CPU_GENERIC && !warned) { |
| Eprintf("Forced cputype %s does not match cpu type %s from mcelog\n", |
| cputype_name[cputype], |
| cputype_name[t]); |
| warned = 1; |
| } |
| } else if (cputype == CPU_GENERIC && !cpu_forced) { |
| is_cpu_supported(); |
| } |
| } |
| |
| static void mce_prepare(struct mce *m) |
| { |
| mce_cpuid(m); |
| if (!m->time) |
| m->time = time(NULL); |
| } |
| |
| static void dump_mce(struct mce *m, unsigned recordlen) |
| { |
| int n; |
| int ismemerr = 0; |
| unsigned cpu = m->extcpu ? m->extcpu : m->cpu; |
| |
| /* should not happen */ |
| if (!m->finished) |
| Wprintf("not finished?\n"); |
| Wprintf("CPU %d %s ", cpu, bankname(m->bank)); |
| if (m->tsc) |
| print_tsc(cpu, m->tsc, m->time); |
| Wprintf("\n"); |
| if (m->ip) |
| Wprintf("RIP%s %02x:%llx\n", |
| !(m->mcgstatus & MCG_STATUS_EIPV) ? " !INEXACT!" : "", |
| m->cs, m->ip); |
| n = 0; |
| if (m->status & MCI_STATUS_MISCV) |
| n += Wprintf("MISC %llx ", m->misc); |
| if (m->status & MCI_STATUS_ADDRV) |
| n += Wprintf("ADDR %llx ", m->addr); |
| if (n > 0) |
| Wprintf("\n"); |
| if (m->time) { |
| time_t t = m->time; |
| Wprintf("TIME %llu %s", m->time, ctime(&t)); |
| } |
| switch (cputype) { |
| case CPU_K8: |
| decode_k8_mc(m, &ismemerr); |
| break; |
| CASE_INTEL_CPUS: |
| decode_intel_mc(m, cputype, &ismemerr, recordlen); |
| break; |
| /* add handlers for other CPUs here */ |
| default: |
| break; |
| } |
| /* decode all status bits here */ |
| Wprintf("STATUS %llx MCGSTATUS %llx\n", m->status, m->mcgstatus); |
| n = 0; |
| if (recordlen >= offsetof(struct mce, cpuid) && m->mcgcap) |
| n += Wprintf("MCGCAP %llx ", m->mcgcap); |
| if (recordlen >= offsetof(struct mce, apicid)) |
| n += Wprintf("APICID %x ", m->apicid); |
| if (recordlen >= offsetof(struct mce, socketid)) |
| n += Wprintf("SOCKETID %x ", m->socketid); |
| if (n > 0) |
| Wprintf("\n"); |
| |
| if (recordlen >= offsetof(struct mce, ppin) && m->ppin) |
| n += Wprintf("PPIN %llx\n", m->ppin); |
| |
| if (recordlen >= offsetof(struct mce, cpuid) && m->cpuid) { |
| u32 fam, mod; |
| parse_cpuid(m->cpuid, &fam, &mod); |
| Wprintf("CPUID Vendor %s Family %u Model %u\n", |
| cpuvendor_name(m->cpuvendor), |
| fam, |
| mod); |
| } |
| if (cputype != CPU_SANDY_BRIDGE_EP && cputype != CPU_IVY_BRIDGE_EPEX && |
| cputype != CPU_HASWELL_EPEX && cputype != CPU_BROADWELL && |
| cputype != CPU_BROADWELL_DE && cputype != CPU_BROADWELL_EPEX && |
| cputype != CPU_KNIGHTS_LANDING && cputype != CPU_KNIGHTS_MILL && |
| cputype != CPU_SKYLAKE && cputype != CPU_SKYLAKE_XEON && |
| cputype != CPU_KABYLAKE && cputype != CPU_DENVERTON) |
| resolveaddr(m->addr); |
| } |
| |
| static void dump_mce_raw_ascii(struct mce *m, unsigned recordlen) |
| { |
| /* should not happen */ |
| if (!m->finished) |
| Wprintf("not finished?\n"); |
| Wprintf("CPU %u\n", m->extcpu ? m->extcpu : m->cpu); |
| Wprintf("BANK %d\n", m->bank); |
| Wprintf("TSC %#llx\n", m->tsc); |
| Wprintf("RIP %#02x:%#llx\n", m->cs, m->ip); |
| Wprintf("MISC %#llx\n", m->misc); |
| Wprintf("ADDR %#llx\n", m->addr); |
| Wprintf("STATUS %#llx\n", m->status); |
| Wprintf("MCGSTATUS %#llx\n", m->mcgstatus); |
| if (recordlen >= offsetof(struct mce, cpuid)) |
| Wprintf("PROCESSOR %u:%#x\n", m->cpuvendor, m->cpuid); |
| #define CPRINT(str, field) \ |
| if (recordlen >= offsetof(struct mce, field)) \ |
| Wprintf(str "\n", m->field) |
| CPRINT("TIME %llu", time); |
| CPRINT("SOCKETID %u", socketid); |
| CPRINT("APICID %u", apicid); |
| CPRINT("MCGCAP %#llx", mcgcap); |
| #undef CPRINT |
| Wprintf("\n"); |
| } |
| |
| int is_cpu_supported(void) |
| { |
| enum { |
| VENDOR = 1, |
| FAMILY = 2, |
| MODEL = 4, |
| MHZ = 8, |
| FLAGS = 16, |
| ALL = 0x1f |
| } seen = 0; |
| FILE *f; |
| static int checked; |
| |
| if (checked) |
| return 1; |
| checked = 1; |
| |
| f = fopen("/proc/cpuinfo","r"); |
| if (f != NULL) { |
| int family = 0; |
| int model = 0; |
| char vendor[64] = { 0 }; |
| char *line = NULL; |
| size_t linelen = 0; |
| double mhz; |
| |
| while (getdelim(&line, &linelen, '\n', f) > 0 && seen != ALL) { |
| if (sscanf(line, "vendor_id : %63[^\n]", vendor) == 1) |
| seen |= VENDOR; |
| if (sscanf(line, "cpu family : %d", &family) == 1) |
| seen |= FAMILY; |
| if (sscanf(line, "model : %d", &model) == 1) |
| seen |= MODEL; |
| /* We use only Mhz of the first CPU, assuming they are the same |
| (there are more sanity checks later to make this not as wrong |
| as it sounds) */ |
| if (sscanf(line, "cpu MHz : %lf", &mhz) == 1) { |
| if (!cpumhz_forced) |
| cpumhz = mhz; |
| seen |= MHZ; |
| } |
| if (!strncmp(line, "flags", 5) && isspace(line[6])) { |
| processor_flags = line; |
| line = NULL; |
| linelen = 0; |
| seen |= FLAGS; |
| } |
| |
| } |
| if (seen == ALL) { |
| if (!strcmp(vendor,"AuthenticAMD")) { |
| if (family == 15) { |
| cputype = CPU_K8; |
| } else if (family >= 16) { |
| Eprintf("ERROR: AMD Processor family %d: mcelog does not support this processor. Please use the edac_mce_amd module instead.\n", family); |
| return 0; |
| } |
| } else if (!strcmp(vendor,"GenuineIntel")) |
| cputype = select_intel_cputype(family, model); |
| /* Add checks for other CPUs here */ |
| } else { |
| Eprintf("warning: Cannot parse /proc/cpuinfo\n"); |
| } |
| fclose(f); |
| free(line); |
| } else |
| Eprintf("warning: Cannot open /proc/cpuinfo\n"); |
| |
| return 1; |
| } |
| |
| static char *skipspace(char *s) |
| { |
| while (isspace(*s)) |
| ++s; |
| return s; |
| } |
| |
| static char *skip_syslog(char *s) |
| { |
| char *p; |
| |
| /* Handle syslog output */ |
| p = strstr(s, "mcelog: "); |
| if (p) |
| return p + sizeof("mcelog: ") - 1; |
| return s; |
| } |
| |
| static char *skipgunk(char *s) |
| { |
| s = skip_syslog(s); |
| |
| s = skipspace(s); |
| if (*s == '<') { |
| s += strcspn(s, ">"); |
| if (*s == '>') |
| ++s; |
| } |
| s = skipspace(s); |
| if (*s == '[') { |
| s += strcspn(s, "]"); |
| if (*s == ']') |
| ++s; |
| } |
| |
| s = skipspace(s); |
| |
| if (strncmp(s, "mce: [Hardware Error]:", 22) == 0) |
| s += 22; |
| |
| return skipspace(s); |
| } |
| |
| static inline int urange(unsigned val, unsigned lo, unsigned hi) |
| { |
| return val >= lo && val <= hi; |
| } |
| |
| static int is_short(char *name) |
| { |
| return strlen(name) == 3 && |
| isupper(name[0]) && |
| islower(name[1]) && |
| islower(name[2]); |
| } |
| |
| static unsigned skip_date(char *s) |
| { |
| unsigned day, hour, min, year, sec; |
| char dayname[11]; |
| char month[11]; |
| unsigned next; |
| |
| if (sscanf(s, "%10s %10s %u %u:%u:%u %u%n", |
| dayname, month, &day, &hour, &min, &sec, &year, &next) != 7) |
| return 0; |
| if (!is_short(dayname) || !is_short(month) || !urange(day, 1, 31) || |
| !urange(hour, 0, 24) || !urange(min, 0, 59) || !urange(sec, 0, 59) || |
| year < 1900) |
| return 0; |
| return next; |
| } |
| |
| static void dump_mce_final(struct mce *m, char *symbol, int missing, int recordlen, |
| int dseen) |
| { |
| m->finished = 1; |
| if (m->cpuid) |
| mce_cpuid(m); |
| if (!dump_raw_ascii) { |
| if (!dseen) |
| disclaimer(); |
| dump_mce(m, recordlen); |
| if (symbol[0]) |
| Wprintf("RIP: %s\n", symbol); |
| if (missing) |
| Wprintf("(Fields were incomplete)\n"); |
| } else |
| dump_mce_raw_ascii(m, recordlen); |
| flushlog(); |
| } |
| |
| static char *skip_patterns[] = { |
| "MCA:*", |
| "MCi_MISC register valid*", |
| "MC? status*", |
| "Unsupported new Family*", |
| "Kernel does not support page offline interface", |
| NULL |
| }; |
| |
| static int match_patterns(char *s, char **pat) |
| { |
| for (; *pat; pat++) |
| if (!fnmatch(*pat, s, 0)) |
| return 0; |
| return 1; |
| } |
| |
| #define FIELD(f) \ |
| if (recordlen < endof_field(struct mce, f)) \ |
| recordlen = endof_field(struct mce, f) |
| |
| /* Decode ASCII input for fatal messages */ |
| static void decodefatal(FILE *inf) |
| { |
| struct mce m; |
| char *line = NULL; |
| size_t linelen = 0; |
| int missing; |
| char symbol[100]; |
| int data; |
| int next; |
| char *s = NULL; |
| unsigned cpuvendor; |
| unsigned recordlen; |
| int disclaimer_seen; |
| |
| ascii_mode = 1; |
| if (do_dmi && dmi_forced) |
| Wprintf( |
| "WARNING: with --dmi mcelog --ascii must run on the same machine with the\n" |
| " same BIOS/memory configuration as where the machine check occurred.\n"); |
| |
| restart: |
| missing = 0; |
| data = 0; |
| next = 0; |
| disclaimer_seen = 0; |
| recordlen = 0; |
| memset(&m, 0, sizeof(struct mce)); |
| symbol[0] = '\0'; |
| while (next > 0 || getdelim(&line, &linelen, '\n', inf) > 0) { |
| int n = 0; |
| char *start; |
| |
| s = next > 0 ? s + next : line; |
| s = skipgunk(s); |
| start = s; |
| next = 0; |
| |
| if (!strncmp(s, "CPU ", 4)) { |
| unsigned cpu = 0, bank = 0; |
| n = sscanf(s, |
| "CPU %u: Machine Check Exception: %16Lx Bank %d: %016Lx%n", |
| &cpu, |
| &m.mcgstatus, |
| &bank, |
| &m.status, |
| &next); |
| if (n == 1) { |
| n = sscanf(s, "CPU %u BANK %u%n", &cpu, &bank, |
| &next); |
| if (n != 2) |
| n = sscanf(s, "CPU %u %u%n", &cpu, |
| &bank, &next); |
| m.cpu = cpu; |
| if (n < 2) |
| missing++; |
| else { |
| m.bank = bank; |
| FIELD(bank); |
| } |
| } else if (n <= 0) { |
| missing++; |
| } else if (n > 1) { |
| FIELD(mcgstatus); |
| m.cpu = cpu; |
| if (n > 2) { |
| m.bank = bank; |
| FIELD(bank); |
| } else if (n > 3) |
| FIELD(status); |
| if (n < 4) |
| missing++; |
| } |
| } |
| else if (!strncmp(s, "STATUS", 6)) { |
| if ((n = sscanf(s,"STATUS %llx%n", &m.status, &next)) < 1) |
| missing++; |
| else |
| FIELD(status); |
| } |
| else if (!strncmp(s, "MCGSTATUS", 6)) { |
| if ((n = sscanf(s,"MCGSTATUS %llx%n", &m.mcgstatus, &next)) < 1) |
| missing++; |
| else |
| FIELD(mcgstatus); |
| } |
| else if (!strncmp(s, "RIP", 3)) { |
| unsigned cs = 0; |
| |
| if (!strncmp(s, "RIP !INEXACT!", 13)) |
| s += 13; |
| else |
| s += 3; |
| |
| n = sscanf(s, "%02x:<%016Lx> {%99s}%n", |
| &cs, |
| &m.ip, |
| symbol, &next); |
| m.cs = cs; |
| if (n < 2) |
| missing++; |
| else |
| FIELD(ip); |
| } |
| else if (!strncmp(s, "TSC",3)) { |
| if ((n = sscanf(s, "TSC %llx%n", &m.tsc, &next)) < 1) |
| missing++; |
| else |
| FIELD(tsc); |
| } |
| else if (!strncmp(s, "ADDR",4)) { |
| if ((n = sscanf(s, "ADDR %llx%n", &m.addr, &next)) < 1) |
| missing++; |
| else |
| FIELD(addr); |
| } |
| else if (!strncmp(s, "MISC",4)) { |
| if ((n = sscanf(s, "MISC %llx%n", &m.misc, &next)) < 1) |
| missing++; |
| else |
| FIELD(misc); |
| } |
| else if (!strncmp(s, "PROCESSOR", 9)) { |
| if ((n = sscanf(s, "PROCESSOR %u:%x%n", &cpuvendor, &m.cpuid, &next)) < 2) |
| missing++; |
| else { |
| m.cpuvendor = cpuvendor; |
| FIELD(cpuid); |
| FIELD(cpuvendor); |
| } |
| } |
| else if (!strncmp(s, "TIME", 4)) { |
| if ((n = sscanf(s, "TIME %llu%n", &m.time, &next)) < 1) |
| missing++; |
| else |
| FIELD(time); |
| |
| next += skip_date(s + next); |
| } |
| else if (!strncmp(s, "MCGCAP", 6)) { |
| if ((n = sscanf(s, "MCGCAP %llx%n", &m.mcgcap, &next)) != 1) |
| missing++; |
| else |
| FIELD(mcgcap); |
| } |
| else if (!strncmp(s, "APICID", 6)) { |
| if ((n = sscanf(s, "APICID %x%n", &m.apicid, &next)) != 1) |
| missing++; |
| else |
| FIELD(apicid); |
| } |
| else if (!strncmp(s, "SOCKETID", 8)) { |
| if ((n = sscanf(s, "SOCKETID %u%n", &m.socketid, &next)) != 1) |
| missing++; |
| else |
| FIELD(socketid); |
| } |
| else if (!strncmp(s, "CPUID", 5)) { |
| unsigned fam, mod; |
| char vendor[31]; |
| |
| if ((n = sscanf(s, "CPUID Vendor %30s Family %u Model %u\n", |
| vendor, &fam, &mod)) < 3) |
| missing++; |
| else { |
| m.cpuvendor = cpuvendor_to_num(vendor); |
| m.cpuid = unparse_cpuid(fam, mod); |
| FIELD(cpuid); |
| FIELD(cpuvendor); |
| } |
| } |
| else if (strstr(s, "HARDWARE ERROR")) |
| disclaimer_seen = 1; |
| else if (!strncmp(s, "(XEN)", 5)) { |
| char *w; |
| unsigned bank, cpu; |
| |
| if (strstr(s, "The hardware reports a non fatal, correctable incident occurred")) { |
| w = strstr(s, "CPU"); |
| if (w && sscanf(w, "CPU %d", &cpu)) { |
| m.cpu = cpu; |
| FIELD(cpu); |
| } |
| } else if ((n = sscanf(s, "(XEN) Bank %d: %llx at %llx", |
| &bank, &m.status, &m.addr) >= 1)) { |
| m.bank = bank; |
| FIELD(bank); |
| if (n >= 2) |
| FIELD(status); |
| if (n >= 3) |
| FIELD(addr); |
| } |
| } |
| else if (!match_patterns(s, skip_patterns)) |
| n = 0; |
| else { |
| s = skipspace(s); |
| if (*s && data) |
| dump_mce_final(&m, symbol, missing, recordlen, disclaimer_seen); |
| if (!dump_raw_ascii) |
| Wprintf("%s", start); |
| if (*s && data) |
| goto restart; |
| } |
| if (n > 0) |
| data = 1; |
| } |
| free(line); |
| if (data) |
| dump_mce_final(&m, symbol, missing, recordlen, disclaimer_seen); |
| } |
| |
| static void remove_pidfile(void) |
| { |
| unlink(pidfile); |
| if (pidfile != pidfile_default) |
| free(pidfile); |
| } |
| |
| static void signal_exit(int sig) |
| { |
| remove_pidfile(); |
| client_cleanup(); |
| _exit(EXIT_SUCCESS); |
| } |
| |
| static void setup_pidfile(char *s) |
| { |
| char cwd[PATH_MAX]; |
| char *c; |
| |
| if (*s != '/') { |
| c = getcwd(cwd, PATH_MAX); |
| if (!c) |
| return; |
| xasprintf(&pidfile, "%s/%s", cwd, s); |
| } else { |
| xasprintf(&pidfile, "%s", s); |
| } |
| |
| return; |
| } |
| |
| static void write_pidfile(void) |
| { |
| FILE *f; |
| atexit(remove_pidfile); |
| signal(SIGTERM, signal_exit); |
| signal(SIGINT, signal_exit); |
| signal(SIGQUIT, signal_exit); |
| f = fopen(pidfile, "w"); |
| if (!f) { |
| Eprintf("Cannot open pidfile `%s'", pidfile); |
| return; |
| } |
| fprintf(f, "%u", getpid()); |
| fclose(f); |
| } |
| |
| void usage(void) |
| { |
| fprintf(stderr, |
| "Usage:\n" |
| "\n" |
| " mcelog [options] [mcelogdevice]\n" |
| "Decode machine check error records from current kernel.\n" |
| "\n" |
| " mcelog [options] --daemon\n" |
| "Run mcelog in daemon mode, waiting for errors from the kernel.\n" |
| "\n" |
| " mcelog [options] --client\n" |
| "Query a currently running mcelog daemon for errors\n" |
| "\n" |
| " mcelog [options] --ascii < log\n" |
| " mcelog [options] --ascii --file log\n" |
| "Decode machine check ASCII output from kernel logs\n" |
| "\n" |
| "Options:\n" |
| "--version Show the version of mcelog and exit\n" |
| "--cpu CPU Set CPU type CPU to decode (see below for valid types)\n" |
| "--intel-cpu FAMILY,MODEL Set CPU type for an Intel CPU based on family and model from cpuid\n" |
| "--k8 Set the CPU to be an AMD K8\n" |
| "--p4 Set the CPU to be an Intel Pentium4\n" |
| "--core2 Set the CPU to be an Intel Core2\n" |
| "--generic Set the CPU to a generic version\n" |
| "--cpumhz MHZ Set CPU Mhz to decode time (output unreliable, not needed on new kernels)\n" |
| "--raw (with --ascii) Dump in raw ASCII format for machine processing\n" |
| "--daemon Run in background waiting for events (needs newer kernel)\n" |
| "--client Query a currently running mcelog daemon for errors\n" |
| "--ignorenodev Exit silently when the device cannot be opened\n" |
| "--file filename With --ascii read machine check log from filename instead of stdin\n" |
| "--logfile filename Log decoded machine checks in file filename\n" |
| "--syslog Log decoded machine checks in syslog (default stdout or syslog for daemon)\n" |
| "--syslog-error Log decoded machine checks in syslog with error level\n" |
| "--no-syslog Never log anything to syslog\n" |
| "--logfile filename Append log output to logfile instead of stdout\n" |
| "--dmi Use SMBIOS information to decode DIMMs (needs root)\n" |
| "--no-dmi Don't use SMBIOS information\n" |
| "--dmi-verbose Dump SMBIOS information (for debugging)\n" |
| "--filter Inhibit known bogus events (default on)\n" |
| "--no-filter Don't inhibit known broken events\n" |
| "--config-file filename Read config information from config file instead of " CONFIG_FILENAME "\n" |
| "--foreground Keep in foreground (for debugging)\n" |
| "--num-errors N Only process N errors (for testing)\n" |
| "--pidfile file Write pid of daemon into file\n" |
| "--no-imc-log Disable extended iMC logging\n" |
| "--is-cpu-supported Exit with return code indicating whether the CPU is supported\n" |
| "--help Display this message.\n" |
| ); |
| printf("\n"); |
| print_cputypes(); |
| } |
| |
| enum options { |
| O_LOGFILE = O_COMMON, |
| O_K8, |
| O_P4, |
| O_GENERIC, |
| O_CORE2, |
| O_INTEL_CPU, |
| O_FILTER, |
| O_DMI, |
| O_NO_DMI, |
| O_DMI_VERBOSE, |
| O_SYSLOG, |
| O_NO_SYSLOG, |
| O_CPUMHZ, |
| O_SYSLOG_ERROR, |
| O_RAW, |
| O_DAEMON, |
| O_ASCII, |
| O_CLIENT, |
| O_VERSION, |
| O_CONFIG_FILE, |
| O_CPU, |
| O_FILE, |
| O_FOREGROUND, |
| O_NUMERRORS, |
| O_PIDFILE, |
| O_DEBUG_NUMERRORS, |
| O_NO_IMC_LOG, |
| O_IS_CPU_SUPPORTED, |
| O_HELP, |
| }; |
| |
| static struct option options[] = { |
| { "logfile", 1, NULL, O_LOGFILE }, |
| { "k8", 0, NULL, O_K8 }, |
| { "p4", 0, NULL, O_P4 }, |
| { "generic", 0, NULL, O_GENERIC }, |
| { "core2", 0, NULL, O_CORE2 }, |
| { "intel-cpu", 1, NULL, O_INTEL_CPU }, |
| { "ignorenodev", 0, &ignore_nodev, 1 }, |
| { "filter", 0, &filter_bogus, 1 }, |
| { "no-filter", 0, &filter_bogus, 0 }, |
| { "dmi", 0, NULL, O_DMI }, |
| { "no-dmi", 0, NULL, O_NO_DMI }, |
| { "dmi-verbose", 1, NULL, O_DMI_VERBOSE }, |
| { "syslog", 0, NULL, O_SYSLOG }, |
| { "cpumhz", 1, NULL, O_CPUMHZ }, |
| { "syslog-error", 0, NULL, O_SYSLOG_ERROR }, |
| { "dump-raw-ascii", 0, &dump_raw_ascii, 1 }, |
| { "raw", 0, &dump_raw_ascii, 1 }, |
| { "no-syslog", 0, NULL, O_NO_SYSLOG }, |
| { "daemon", 0, NULL, O_DAEMON }, |
| { "ascii", 0, NULL, O_ASCII }, |
| { "file", 1, NULL, O_FILE }, |
| { "version", 0, NULL, O_VERSION }, |
| { "config-file", 1, NULL, O_CONFIG_FILE }, |
| { "cpu", 1, NULL, O_CPU }, |
| { "foreground", 0, NULL, O_FOREGROUND }, |
| { "client", 0, NULL, O_CLIENT }, |
| { "num-errors", 1, NULL, O_NUMERRORS }, |
| { "pidfile", 1, NULL, O_PIDFILE }, |
| { "debug-numerrors", 0, NULL, O_DEBUG_NUMERRORS }, /* undocumented: for testing */ |
| { "no-imc-log", 0, NULL, O_NO_IMC_LOG }, |
| { "help", 0, NULL, O_HELP }, |
| { "is-cpu-supported", 0, NULL, O_IS_CPU_SUPPORTED }, |
| {} |
| }; |
| |
| static int modifier(int opt) |
| { |
| int v; |
| |
| switch (opt) { |
| case O_LOGFILE: |
| logfile = optarg; |
| break; |
| case O_K8: |
| cputype = CPU_K8; |
| cpu_forced = 1; |
| break; |
| case O_P4: |
| cputype = CPU_P4; |
| cpu_forced = 1; |
| break; |
| case O_GENERIC: |
| cputype = CPU_GENERIC; |
| cpu_forced = 1; |
| break; |
| case O_CORE2: |
| cputype = CPU_CORE2; |
| cpu_forced = 1; |
| break; |
| case O_INTEL_CPU: { |
| unsigned fam, mod; |
| if (sscanf(optarg, "%i,%i", &fam, &mod) != 2) { |
| usage(); |
| exit(1); |
| } |
| cputype = select_intel_cputype(fam, mod); |
| if (cputype == CPU_GENERIC) { |
| fprintf(stderr, "Unknown Intel CPU\n"); |
| usage(); |
| exit(1); |
| } |
| cpu_forced = 1; |
| break; |
| } |
| case O_CPU: |
| cputype = lookup_cputype(optarg); |
| cpu_forced = 1; |
| intel_cpu_init(cputype); |
| break; |
| case O_DMI: |
| do_dmi = 1; |
| dmi_forced = 1; |
| break; |
| case O_NO_DMI: |
| dmi_forced = 1; |
| do_dmi = 0; |
| break; |
| case O_DMI_VERBOSE: |
| if (sscanf(optarg, "%i", &v) != 1) { |
| usage(); |
| exit(1); |
| } |
| dmi_set_verbosity(v); |
| break; |
| case O_SYSLOG: |
| openlog("mcelog", 0, LOG_DAEMON); |
| syslog_opt = SYSLOG_ALL|SYSLOG_FORCE; |
| break; |
| case O_NO_SYSLOG: |
| syslog_opt = SYSLOG_FORCE; |
| break; |
| case O_CPUMHZ: |
| cpumhz_forced = 1; |
| if (sscanf(optarg, "%lf", &cpumhz) != 1) { |
| usage(); |
| exit(1); |
| } |
| break; |
| case O_SYSLOG_ERROR: |
| syslog_level = LOG_ERR; |
| syslog_opt = SYSLOG_ALL|SYSLOG_FORCE; |
| break; |
| case O_DAEMON: |
| daemon_mode = 1; |
| if (!(syslog_opt & SYSLOG_FORCE)) |
| syslog_opt = SYSLOG_REMARK|SYSLOG_ERROR; |
| break; |
| case O_FILE: |
| inputfile = optarg; |
| break; |
| case O_FOREGROUND: |
| foreground = 1; |
| if (!(syslog_opt & SYSLOG_FORCE)) |
| syslog_opt = SYSLOG_FORCE; |
| break; |
| case O_NUMERRORS: |
| numerrors = atoi(optarg); |
| break; |
| case O_PIDFILE: |
| setup_pidfile(optarg); |
| break; |
| case O_CONFIG_FILE: |
| /* parsed in config.c */ |
| break; |
| case O_DEBUG_NUMERRORS: |
| debug_numerrors = 1; |
| break; |
| case O_NO_IMC_LOG: |
| imc_log = 0; |
| break; |
| case O_IS_CPU_SUPPORTED: |
| check_only = 1; |
| break; |
| case O_HELP: |
| usage(); |
| exit(0); |
| break; |
| case 0: |
| break; |
| default: |
| return 0; |
| } |
| return 1; |
| } |
| |
| static void modifier_finish(void) |
| { |
| if(!foreground && daemon_mode && !logfile && !(syslog_opt & SYSLOG_LOG)) { |
| logfile = logfile_default; |
| } |
| if (logfile) { |
| if (open_logfile(logfile) < 0) { |
| if (daemon_mode && !(syslog_opt & SYSLOG_FORCE)) |
| syslog_opt = SYSLOG_ALL; |
| SYSERRprintf("Cannot open logfile %s", logfile); |
| if (!daemon_mode) |
| exit(1); |
| } |
| } |
| } |
| |
| void argsleft(int ac, char **av) |
| { |
| int opt; |
| |
| while ((opt = getopt_long(ac, av, "", options, NULL)) != -1) { |
| if (modifier(opt) != 1) { |
| usage(); |
| exit(1); |
| } |
| } |
| } |
| |
| void no_syslog(void) |
| { |
| if (!(syslog_opt & SYSLOG_FORCE)) |
| syslog_opt = 0; |
| } |
| |
| static int combined_modifier(int opt) |
| { |
| int r = modifier(opt); |
| return r; |
| } |
| |
| static void general_setup(void) |
| { |
| trigger_setup(); |
| yellow_setup(); |
| bus_setup(); |
| unknown_setup(); |
| config_cred("global", "run-credentials", &runcred); |
| if (config_bool("global", "filter-memory-errors") == 1) |
| filter_memory_errors = 1; |
| } |
| |
| static void drop_cred(void) |
| { |
| if (runcred.uid != -1U && runcred.gid == -1U) { |
| struct passwd *pw = getpwuid(runcred.uid); |
| if (pw) |
| runcred.gid = pw->pw_gid; |
| } |
| if (runcred.gid != -1U) { |
| if (setgid(runcred.gid) < 0) |
| SYSERRprintf("Cannot change group to %d", runcred.gid); |
| } |
| if (runcred.uid != -1U) { |
| if (setuid(runcred.uid) < 0) |
| SYSERRprintf("Cannot change user to %d", runcred.uid); |
| } |
| } |
| |
| static void process(int fd, unsigned recordlen, unsigned loglen, char *buf) |
| { |
| int i; |
| int len, count; |
| int finish = 0, flags; |
| |
| if (recordlen == 0) { |
| Wprintf("no data in mce record\n"); |
| return; |
| } |
| |
| len = read(fd, buf, recordlen * loglen); |
| if (len < 0) { |
| SYSERRprintf("mcelog read"); |
| return; |
| } |
| |
| count = len / (int)recordlen; |
| if (count == (int)loglen) { |
| if ((ioctl(fd, MCE_GETCLEAR_FLAGS, &flags) == 0) && |
| (flags & (1 << MCE_OVERFLOW))) |
| Eprintf("Warning: MCE buffer is overflowed.\n"); |
| } |
| |
| for (i = 0; (i < count) && !finish; i++) { |
| struct mce *mce = (struct mce *)(buf + i*recordlen); |
| mce_prepare(mce); |
| if (numerrors > 0 && --numerrors == 0) |
| finish = 1; |
| if (!mce_filter(mce, recordlen)) |
| continue; |
| if (!dump_raw_ascii) { |
| disclaimer(); |
| Wprintf("MCE %d\n", i); |
| dump_mce(mce, recordlen); |
| } else |
| dump_mce_raw_ascii(mce, recordlen); |
| flushlog(); |
| } |
| |
| if (debug_numerrors && numerrors <= 0) |
| finish = 1; |
| |
| if (recordlen > sizeof(struct mce)) { |
| Eprintf("warning: %lu bytes ignored in each record\n", |
| (unsigned long)recordlen - sizeof(struct mce)); |
| Eprintf("consider an update\n"); |
| } |
| |
| if (finish) |
| exit(0); |
| } |
| |
| static void noargs(int ac, char **av) |
| { |
| if (getopt_long(ac, av, "", options, NULL) != -1) { |
| usage(); |
| exit(1); |
| } |
| } |
| |
| static void parse_config(char **av) |
| { |
| static const char config_fn[] = CONFIG_FILENAME; |
| const char *fn = config_file(av, config_fn); |
| if (!fn) { |
| usage(); |
| exit(1); |
| } |
| if (parse_config_file(fn) < 0) { |
| /* If it's the default file don't complain if it isn't there */ |
| if (fn != config_fn) { |
| fprintf(stderr, "Cannot open config file %s\n", fn); |
| exit(1); |
| } |
| return; |
| } |
| config_options(options, combined_modifier); |
| } |
| |
| static void ascii_command(int ac, char **av) |
| { |
| FILE *f = stdin; |
| |
| argsleft(ac, av); |
| if (inputfile) { |
| f = fopen(inputfile, "r"); |
| if (!f) { |
| fprintf(stderr, "Cannot open input file `%s': %s\n", |
| inputfile, strerror(errno)); |
| exit(1); |
| } |
| /* f closed by exit */ |
| } |
| no_syslog(); |
| checkdmi(); |
| decodefatal(f); |
| } |
| |
| static void client_command(int ac, char **av) |
| { |
| argsleft(ac, av); |
| no_syslog(); |
| // XXX modifiers |
| ask_server("dump all bios\n"); |
| ask_server("pages\n"); |
| } |
| |
| struct mcefd_data { |
| unsigned loglen; |
| unsigned recordlen; |
| char *buf; |
| }; |
| |
| static void process_mcefd(struct pollfd *pfd, void *data) |
| { |
| struct mcefd_data *d = (struct mcefd_data *)data; |
| assert((pfd->revents & POLLIN) != 0); |
| process(pfd->fd, d->recordlen, d->loglen, d->buf); |
| } |
| |
| static void handle_sigusr1(int sig) |
| { |
| reopenlog(); |
| } |
| |
| int main(int ac, char **av) |
| { |
| struct mcefd_data d = {}; |
| int opt; |
| int fd; |
| |
| parse_config(av); |
| |
| while ((opt = getopt_long(ac, av, "", options, NULL)) != -1) { |
| if (opt == '?') { |
| usage(); |
| exit(1); |
| } else if (combined_modifier(opt) > 0) { |
| continue; |
| } else if (opt == O_ASCII) { |
| ascii_command(ac, av); |
| exit(0); |
| } else if (opt == O_CLIENT) { |
| client_command(ac, av); |
| exit(0); |
| } else if (opt == O_VERSION) { |
| noargs(ac, av); |
| fprintf(stderr, "mcelog %s\n", MCELOG_VERSION); |
| exit(0); |
| } else if (opt == 0) |
| break; |
| } |
| |
| /* before doing anything else let's see if the CPUs are supported */ |
| if (!cpu_forced && !is_cpu_supported()) { |
| if (!check_only) |
| fprintf(stderr, "CPU is unsupported\n"); |
| exit(1); |
| } |
| if (check_only) |
| exit(0); |
| |
| /* If the user didn't tell us not to use iMC logging, check if CPU supports it */ |
| if (imc_log == -1) { |
| switch (cputype) { |
| case CPU_SANDY_BRIDGE_EP: |
| case CPU_IVY_BRIDGE_EPEX: |
| case CPU_HASWELL_EPEX: |
| imc_log = 1; |
| break; |
| default: |
| imc_log = 0; |
| break; |
| } |
| } |
| |
| modifier_finish(); |
| if (av[optind]) |
| logfn = av[optind++]; |
| if (av[optind]) { |
| usage(); |
| exit(1); |
| } |
| checkdmi(); |
| general_setup(); |
| |
| fd = open(logfn, O_RDONLY); |
| if (fd < 0) { |
| if (ignore_nodev) |
| exit(0); |
| SYSERRprintf("Cannot open `%s'", logfn); |
| exit(1); |
| } |
| |
| if (ioctl(fd, MCE_GET_RECORD_LEN, &d.recordlen) < 0) |
| err("MCE_GET_RECORD_LEN"); |
| if (ioctl(fd, MCE_GET_LOG_LEN, &d.loglen) < 0) |
| err("MCE_GET_LOG_LEN"); |
| |
| d.buf = xalloc(d.recordlen * d.loglen); |
| if (daemon_mode) { |
| prefill_memdb(do_dmi); |
| if (!do_dmi) |
| closedmi(); |
| server_setup(); |
| page_setup(); |
| if (imc_log) |
| set_imc_log(cputype); |
| drop_cred(); |
| register_pollcb(fd, POLLIN, process_mcefd, &d); |
| if (!foreground && daemon(0, need_stdout()) < 0) |
| err("daemon"); |
| if (pidfile) |
| write_pidfile(); |
| signal(SIGUSR1, handle_sigusr1); |
| event_signal(SIGUSR1); |
| eventloop(); |
| } else { |
| process(fd, d.recordlen, d.loglen, d.buf); |
| } |
| trigger_wait(); |
| |
| exit(0); |
| } |