From 2fd3eec87cb97967f911c2b240d279b67d33c774 Mon Sep 17 00:00:00 2001 From: Bastiaan van Kesteren Date: Sun, 7 Feb 2021 12:01:07 +0100 Subject: [PATCH 1/4] Hacked together a smart_ plugin in C There is some trickery going on to not wakeup the disk when it's in standby Note: this was aimed at munin-c, but was rejected since it uses a subprocess that calls the `smartctl` tool. --- plugins/disk/smart-c/smart.c | 201 +++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 plugins/disk/smart-c/smart.c diff --git a/plugins/disk/smart-c/smart.c b/plugins/disk/smart-c/smart.c new file mode 100644 index 00000000..ed057360 --- /dev/null +++ b/plugins/disk/smart-c/smart.c @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2017 Bastiaan van Kesteren - All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU General Public License v.2 or v.3. + */ + +#include +#include +#include +#include +#include +#include +#include "common.h" + +static char getitem(char *input, unsigned char item, char *output) +{ + unsigned char i = 0; + unsigned char seperators = 0; + char know_this_seperator = 0; + unsigned char start = 0; + unsigned char stop = 0; + + /* Trim starting spaces */ + while (input[i] == ' ') { + i++; + } + + /* If we're requested to return the very first item... */ + if (seperators == item) { + start = i; + } + + while (input[i] && seperators < item + 1) { + if (input[i] == ' ') { + if (know_this_seperator == 0) { + know_this_seperator = 1; + seperators++; + if (seperators == item + 1) { + stop = i; + break; + } + } + } else if (know_this_seperator) { + know_this_seperator = 0; + if (seperators == item) { + start = i; + } + } else if (input[i] == '\n') { + input[i] = 0; + break; + } + + i++; + } + + if (stop) { + /* Found stop, means we have a start aswell */ + strncpy(output, &input[start], stop - start); + return 1; + } else if (start) { + /* Have a start, no stop. We're returning the last item of the string */ + strcpy(output, &input[start]); + return 1; + } + + return 0; +} + +int smart(int argc, char **argv) +{ + char command[50]; + char output[255]; + char label[25][100]; + char value[25][25]; + unsigned char attribute = 0; + FILE *f; + unsigned int i; + + /* Prepare and execute command */ + snprintf(command, sizeof(command), + "smartctl -A -d ata /dev/%s -n standby", + &basename(argv[0])[6]); + if ((f = popen(command, "r")) == 0) { + return fail("cannot initiate command execution"); + } + + /* Process command output */ + while (fgets(output, sizeof(output), f) != NULL) { + printf("#%s", output); + /* Filter out attribute lines; look for lines starting with an attribute ID */ + if ((output[0] >= '0' && output[0] <= '9') || + (output[0] == ' ' + && (output[1] >= '0' && output[1] <= '9')) + || (output[0] == ' ' && output[1] == ' ' + && (output[2] >= '0' && output[2] <= '9'))) { + /* Now, print the 2nd column (attribute name) and the 10th (raw value) */ + + getitem(output, 1, label[attribute]); + getitem(output, 9, value[attribute]); + attribute++; + if (attribute == 25) { + break; + } + } + } + + /* Close command (this is where we get the exit code! */ + i = WEXITSTATUS(pclose(f)); + if (i == 1 || /* smartctl command did not parse */ + /*i == 2 || *//* smartctl device open failed */ + i == 127) { /* command not found */ + return fail("command execution failed"); + } + + /* Setup for caching */ + snprintf(command, sizeof(command), "/mnt/ram/smart_%s", + &basename(argv[0])[6]); + + if (attribute == 0) { + printf("#Cached attributes\n"); + /* No output from command, try to fetch attribute-list from disk with NaN values */ + if ((f = fopen(command, "r")) == 0) { + return + fail + ("command did not return data, no cached attribute-list"); + return 0; + } + } + + if (argc > 1) { + if (strcmp(argv[1], "config") == 0) { + printf + ("graph_title S.M.A.R.T values for drive %s\n" + "graph_args --base 1000 --lower-limit 0\n" + "graph_vlabel Attribute S.M.A.R.T value\n" + "graph_category disk\n", + &basename(argv[0])[6]); + + if (attribute == 0) { + while (fgets(output, sizeof(output), f) != + NULL) { + for (i = 0; i < strlen(output); + i++) { + if (output[i] == '\n') { + output[i] = '\0'; + break; + } + } + printf("%s.label %s\n", output, + output); + } + fclose(f); + } else { + f = fopen(command, "w"); + do { + attribute--; + printf("%s.label %s\n", + label[attribute], + label[attribute]); + if (f) { + fprintf(f, "%s\n", + label[attribute]); + } + } while (attribute); + + if (f) { + fclose(f); + } + } + printf("standby.label standby\n"); + return 0; + } + } + + /* Asking for a fetch */ + if (attribute == 0) { + /* No data, use cached info */ + while (fgets(output, sizeof(output), f) != NULL) { + for (i = 0; i < strlen(output); i++) { + if (output[i] == '\n') { + output[i] = '\0'; + break; + } + } + printf("%s.value U\n", output); + } + printf("standby.value 1\n"); + fclose(f); + } else { + do { + attribute--; + printf("%s.value %s\n", label[attribute], + value[attribute]); + } while (attribute); + printf("standby.value 0\n"); + } + + return 0; +} From a420c4eb58500282398508c204f9e0d4f0c5c36c Mon Sep 17 00:00:00 2001 From: Steve Schnepp Date: Sun, 7 Feb 2021 12:04:07 +0100 Subject: [PATCH 2/4] smart_: renaming the file The destination file is a wildcard one. Therefore the C file should be named with a trailing `_`. --- plugins/disk/smart-c/{smart.c => smart_.c} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/disk/smart-c/{smart.c => smart_.c} (100%) diff --git a/plugins/disk/smart-c/smart.c b/plugins/disk/smart-c/smart_.c similarity index 100% rename from plugins/disk/smart-c/smart.c rename to plugins/disk/smart-c/smart_.c From 6eff786eeff513814e0b205b00a4b97423e308b8 Mon Sep 17 00:00:00 2001 From: Steve Schnepp Date: Sun, 7 Feb 2021 12:05:40 +0100 Subject: [PATCH 3/4] smart: importing common.{h,c} from munin-c The compilation is very easy, a simple `make` should work, as I provided a convenient `Makefile` --- plugins/disk/smart-c/Makefile | 6 +++ plugins/disk/smart-c/common.c | 93 +++++++++++++++++++++++++++++++++++ plugins/disk/smart-c/common.h | 45 +++++++++++++++++ plugins/disk/smart-c/smart_.c | 2 +- 4 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 plugins/disk/smart-c/Makefile create mode 100644 plugins/disk/smart-c/common.c create mode 100644 plugins/disk/smart-c/common.h diff --git a/plugins/disk/smart-c/Makefile b/plugins/disk/smart-c/Makefile new file mode 100644 index 00000000..f1a52a82 --- /dev/null +++ b/plugins/disk/smart-c/Makefile @@ -0,0 +1,6 @@ +smart_: smart_.o common.o + +.PHONY: clean + +clean: + rm -f smart_ smart_.o common.o diff --git a/plugins/disk/smart-c/common.c b/plugins/disk/smart-c/common.c new file mode 100644 index 00000000..39ba514d --- /dev/null +++ b/plugins/disk/smart-c/common.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2008-2013 Helmut Grohne - All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU General Public License v.2 or v.3. + */ +#include +#include +#include +#include +#include +#include "common.h" + +extern char **environ; + +int writeyes(void) +{ + puts("yes"); + return 0; +} + +int autoconf_check_readable(const char *path) +{ + if (0 == access(path, R_OK)) + return writeyes(); + else { + printf("no (%s is not readable, errno=%d)\n", path, errno); + return 0; + } +} + +int getenvint(const char *name, int defvalue) +{ + const char *value; + value = getenv(name); + if (value == NULL) + return defvalue; + return atoi(value); +} + +static + /*@null@ */ + /*@observer@ */ +const char *getenv_composed(const char *name1, const char *name2) +{ + char **p; + size_t len1 = strlen(name1), len2 = strlen(name2); + for (p = environ; *p; ++p) { + if (0 == strncmp(*p, name1, len1) && + 0 == strncmp(len1 + *p, name2, len2) && + (*p)[len1 + len2] == '=') + return len1 + len2 + 1 + *p; + } + return NULL; +} + +void print_warning(const char *name) +{ + const char *p; + p = getenv_composed(name, "_warning"); + if (p == NULL) + p = getenv("warning"); + if (p == NULL) + return; + + printf("%s.warning %s\n", name, p); +} + +void print_critical(const char *name) +{ + const char *p; + p = getenv_composed(name, "_critical"); + if (p == NULL) + p = getenv("critical"); + if (p == NULL) + return; + + printf("%s.critical %s\n", name, p); +} + +void print_warncrit(const char *name) +{ + print_warning(name); + print_critical(name); +} + +int fail(const char *message) +{ + fputs(message, stderr); + fputc('\n', stderr); + return 1; +} diff --git a/plugins/disk/smart-c/common.h b/plugins/disk/smart-c/common.h new file mode 100644 index 00000000..34ca0541 --- /dev/null +++ b/plugins/disk/smart-c/common.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2008 Helmut Grohne - All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU General Public License v.2 or v.3. + */ +#ifndef COMMON_H +#define COMMON_H + +#define PROC_STAT "/proc/stat" + +/** Write yes to stdout and return 0. The intended use is give an autoconf + * response like "return writeyes();". + * @returns a success state to be passed on as the return value from main */ +int writeyes(void); + +/** Answer an autoconf request by checking the readability of the given file. + */ +int autoconf_check_readable(const char *); + +/** Obtain an integer value from the environment. In the absence of the + * variable the given defaultvalue is returned. */ +int getenvint(const char *, int defaultvalue); + +/** Print a name.warning line using the "name_warning" or "warning" environment + * variables. */ +void print_warning(const char *name); + +/** Print a name.critical line using the "name_critical" or "critical" + * environment variables. */ +void print_critical(const char *name); + +/** Print both name.warning and name.critical lines using environment + * variables. */ +void print_warncrit(const char *name); + +/** Fail by printing the given message and a newline to stderr. + * @returns a failure state to be passed on as the return value from main */ +int fail(const char *message); + +#define xisspace(x) isspace((int)(unsigned char) x) +#define xisdigit(x) isdigit((int)(unsigned char) x) + +#endif diff --git a/plugins/disk/smart-c/smart_.c b/plugins/disk/smart-c/smart_.c index ed057360..a474259b 100644 --- a/plugins/disk/smart-c/smart_.c +++ b/plugins/disk/smart-c/smart_.c @@ -68,7 +68,7 @@ static char getitem(char *input, unsigned char item, char *output) return 0; } -int smart(int argc, char **argv) +int main(int argc, char **argv) { char command[50]; char output[255]; From b86f1d0ffec54f24b081d2e595a0e0e1495c8205 Mon Sep 17 00:00:00 2001 From: Steve Schnepp Date: Sun, 7 Feb 2021 12:19:39 +0100 Subject: [PATCH 4/4] smart_: port to openbsd & macos --- plugins/disk/smart-c/smart_.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/disk/smart-c/smart_.c b/plugins/disk/smart-c/smart_.c index a474259b..007219dd 100644 --- a/plugins/disk/smart-c/smart_.c +++ b/plugins/disk/smart-c/smart_.c @@ -107,7 +107,10 @@ int main(int argc, char **argv) } /* Close command (this is where we get the exit code! */ - i = WEXITSTATUS(pclose(f)); + { + int status = pclose(f); /* using an explicit temp var, to be compatible with macos & openbsd */ + i = WEXITSTATUS(status); + } if (i == 1 || /* smartctl command did not parse */ /*i == 2 || *//* smartctl device open failed */ i == 127) { /* command not found */