/* * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * For more information, please refer to * ************************************************************************** * * To compile: * Console mode: gcc -Wall -O3 -o hddled hddled.c * X mode: gcc -Wall -O3 -DX -lX11 -o hddled hddled.c * * Options: * -d, --detach Detach from terminal * -r, --refresh=VALUE Refresh interval (default: 50 ms) * * Console options: * -C, --console=DEVICE Console device (default: /dev/console) * * X options: * -D, --display=DISPLAY X display (default: $DISPLAY) */ #define PACKAGE_STRING "hddled 0.3" #define PACKAGE_BUGREPORT "hddled@very.puzzling.org" #define VMSTAT "/proc/vmstat" #define _GNU_SOURCE #include #include #include #include #include #include #include #ifdef X # include # include # define SCROLL_LOCK 3 /* X display connection */ typedef Display *data_t; static char *o_display = NULL; #else # include # include # include # define CONSOLE "/dev/console" /* File descriptor */ typedef int data_t; static char *o_console = NULL; #endif const char *argp_program_version = PACKAGE_STRING; const char *argp_program_bug_address = "<" PACKAGE_BUGREPORT ">"; static unsigned int o_refresh = 50; /* milliseconds */ static int o_detach = 0; static volatile sig_atomic_t running = 1; static char *line = NULL; static size_t len = 0; /* Reread the vmstat file */ int activity(FILE *vmstat) { static unsigned int prev_pgpgin, prev_pgpgout; unsigned int pgpgin, pgpgout; int found_pgpgin, found_pgpgout; int result; /* Reload the vmstat file */ result = TEMP_FAILURE_RETRY(fseek(vmstat, 0L, SEEK_SET)); if (result) { perror("Could not rewind " VMSTAT); return result; } /* Clear glibc's buffer */ result = TEMP_FAILURE_RETRY(fflush(vmstat)); if (result) { perror("Could not flush input stream"); return result; } /* Extract the I/O stats */ found_pgpgin = found_pgpgout = 0; while (getline(&line, &len, vmstat) != -1 && errno != EINTR) { if (sscanf(line, "pgpgin %u", &pgpgin)) found_pgpgin++; else if (sscanf(line, "pgpgout %u", &pgpgout)) found_pgpgout++; if (found_pgpgin && found_pgpgout) break; } if (!found_pgpgin || !found_pgpgout) { fprintf(stderr, "Could not find required lines in " VMSTAT); return -1; } /* Anything changed? */ result = (prev_pgpgin != pgpgin) || (prev_pgpgout != pgpgout); prev_pgpgin = pgpgin; prev_pgpgout = pgpgout; return result; } /* Update the keyboard LED */ void led(data_t data, int on) { static int current = 1; /* Ensure the LED turns off on first call */ if (current == on) return; #ifdef X { static XKeyboardControl control; control.led = SCROLL_LOCK; control.led_mode = on ? LedModeOn : LedModeOff; XChangeKeyboardControl(data, KBLed | KBLedMode, &control); XFlush(data); } #else { unsigned long state; ioctl(data, KDGETLED, &state); if (on) state |= LED_SCR; else state &= ~LED_SCR; ioctl(data, KDSETLED, state); } #endif current = on; } /* Signal handler -- break out of the main loop */ void shutdown(int sig) { running = 0; } /* Argp parser function */ error_t parse_options(int key, char *arg, struct argp_state *state) { switch (key) { case 'd': o_detach = 1; break; case 'r': o_refresh = strtol(arg, NULL, 10); if (o_refresh < 10) argp_failure(state, EXIT_FAILURE, 0, "refresh interval must be at least 10"); break; #ifdef X case 'D': o_display = strdup(arg); break; #else case 'C': o_console = strdup(arg); break; #endif } return 0; } int main(int argc, char **argv) { struct argp_option options[] = { { "detach", 'd', NULL, 0, "Detach from terminal" }, { "refresh", 'r', "VALUE", 0, "Refresh interval (default: 50 ms)" }, #ifdef X { "display", 'D', "DISPLAY", 0, "X display (default: $DISPLAY)", 1 }, #else { "console", 'C', "DEVICE", 0, "Console device (default: " CONSOLE ")", 1 }, #endif { 0 }, }; struct argp parser = { NULL, parse_options, NULL, "Show hard disk activity using the Scroll Lock LED.", NULL, NULL, NULL }; int status = EXIT_FAILURE; FILE *vmstat = NULL; data_t data = 0; struct timespec delay; /* Parse the command-line */ parser.options = options; if (argp_parse(&parser, argc, argv, ARGP_NO_ARGS, NULL, NULL)) goto out; delay.tv_sec = o_refresh / 1000; delay.tv_nsec = 1000000 * (o_refresh % 1000); #ifdef X /* Open the X display */ data = XOpenDisplay(o_display); if (data == NULL) { if (o_display) fprintf(stderr, "Could not open display: %s\n", o_display); else fprintf(stderr, "Could not open default display\n"); goto out; } #else /* Open the console */ data = open(o_console ? o_console : CONSOLE, O_RDONLY); if (data < 0) { fprintf(stderr, "Could not open console: %s: %s\n", o_console ? o_console : CONSOLE, strerror(errno)); goto out; } #endif /* Open the vmstat file */ vmstat = fopen(VMSTAT, "r"); if (!vmstat) { perror("Could not open " VMSTAT " for reading"); goto out; } /* Ensure the LED is off */ led(data, 0); /* Save the current I/O stat values */ if (activity(vmstat) < 0) goto out; /* Detach from terminal? */ if (o_detach) { pid_t child = fork(); if (child < 0) { perror("Could not detach from terminal"); goto out; } if (child) { /* I am the parent */ #ifdef X /* Don't close the display on my child */ data = NULL; #endif status = EXIT_SUCCESS; goto out; } } /* We catch these signals so we can clean up */ { struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_handler = shutdown; sigemptyset(&action.sa_mask); action.sa_flags = 0; /* We block on usleep; don't use SA_RESTART */ sigaction(SIGHUP, &action, NULL); sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); } /* Loop until signal received */ while (running) { int a; if (nanosleep(&delay, NULL) < 0) break; a = activity(vmstat); if (a < 0) break; led(data, a); } /* Ensure the LED is off */ led(data, 0); status = EXIT_SUCCESS; out: if (line) free(line); if (vmstat) fclose(vmstat); #ifdef X if (data) XCloseDisplay(data); if (o_display) free(o_display); #else if (data) close(data); if (o_console) free(o_console); #endif return status; }