170 lines
4.9 KiB
C
170 lines
4.9 KiB
C
/*
|
|
* poweroff_guard - Simple yet effective guardian to prevent accidentally
|
|
* powering off the wrong system because let's face it, you're stupid.
|
|
*
|
|
* Copyright (c) 2022 anna <owo@fef.moe>.
|
|
* See the end of this file for license terms.
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200112L
|
|
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <unistd.h>
|
|
|
|
#ifndef POWEROFF_PATH
|
|
#define POWEROFF_PATH "/sbin/poweroff"
|
|
#endif
|
|
#ifndef COUNTDOWN_SECS
|
|
#define COUNTDOWN_SECS 10
|
|
#endif
|
|
|
|
#define red(s) "\x1b[1;91m" s "\x1b[0m" /* bright red */
|
|
#define cyn(s) "\x1b[1;96m" s "\x1b[0m" /* bright cyan */
|
|
#define wht(s) "\x1b[1;97m" s "\x1b[0m" /* bright white */
|
|
|
|
enum confirm_match {
|
|
CONFIRM_MATCH_UPPERCASE, /* exact match ("YES") */
|
|
CONFIRM_MATCH_LOWERCASE, /* case-insensitive match */
|
|
CONFIRM_MATCH_NONE, /* no match */
|
|
};
|
|
|
|
/* read and evaluate confirmation answer from stdin */
|
|
static enum confirm_match get_confirmation(void);
|
|
/* count down from COUNTDOWN_SECS and do the actual poweroff afterwards */
|
|
static void poweroff_with_countdown(void);
|
|
/* do the actual poweroff, without countdown */
|
|
static void do_poweroff(void);
|
|
/* call perror(message) and then exit(status) */
|
|
static void perror_and_exit(int status, const char *message);
|
|
|
|
static char hostname[HOST_NAME_MAX];
|
|
|
|
int main(void)
|
|
{
|
|
/* behave like regular poweroff if we're not running in a tty */
|
|
if (!isatty(STDOUT_FILENO))
|
|
do_poweroff();
|
|
|
|
int err = gethostname(hostname, sizeof(hostname));
|
|
if (err) {
|
|
perror("Could not determine hostname");
|
|
strncpy(hostname, "this host", sizeof(hostname));
|
|
}
|
|
|
|
puts(red("\n######## ATTENTION, DIPSHIT ########\n"));
|
|
printf("You are about to shut down " cyn("%s") "!\n", hostname);
|
|
puts("Are you really sure about this?");
|
|
printf("Type " wht("yes") " in UPPERCASE and press enter to confirm: ");
|
|
fflush(stdout);
|
|
|
|
int try = 0;
|
|
again:
|
|
switch (get_confirmation()) {
|
|
case CONFIRM_MATCH_UPPERCASE:
|
|
poweroff_with_countdown();
|
|
break;
|
|
case CONFIRM_MATCH_LOWERCASE:
|
|
if (try++ == 0) {
|
|
printf("I said " wht("UPPERCASE") " you idiot, try again: ");
|
|
fflush(stdout);
|
|
goto again; /* it's very important to piss off Dijkstra */
|
|
} else {
|
|
puts("Go fuck yourself");
|
|
return 0;
|
|
}
|
|
case CONFIRM_MATCH_NONE:
|
|
puts("That's what I thought.");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static char buf[5];
|
|
|
|
static enum confirm_match get_confirmation(void)
|
|
{
|
|
char *input = fgets(buf, sizeof(buf), stdin);
|
|
if (!input) {
|
|
if (feof(stdin)) {
|
|
/* user probably pressed CTRL+D, print
|
|
* a line break to make it look nicer */
|
|
printf("\n");
|
|
return CONFIRM_MATCH_NONE;
|
|
} else {
|
|
perror_and_exit(1, "Reading stdin failed");
|
|
}
|
|
}
|
|
|
|
/* if, for some reason, the input came from a pipe rather than typing
|
|
* on the keyboard (cat, echo, etc), the newline might be missing */
|
|
char *newline = strchr(input, '\n');
|
|
if (newline)
|
|
*newline = '\0';
|
|
else
|
|
printf("\n");
|
|
|
|
if (!strcmp(input, "YES"))
|
|
return CONFIRM_MATCH_UPPERCASE;
|
|
if (!strcasecmp(input, "yes"))
|
|
return CONFIRM_MATCH_LOWERCASE;
|
|
return CONFIRM_MATCH_NONE;
|
|
}
|
|
|
|
static void poweroff_with_countdown(void)
|
|
{
|
|
#if COUNTDOWN_SECS > 0
|
|
printf("Alright, shutting down in ");
|
|
for (int i = COUNTDOWN_SECS; i > 0; i--) {
|
|
printf(wht("%d") "\n", i);
|
|
fflush(stdout);
|
|
sleep(1);
|
|
}
|
|
#else
|
|
puts("Alright, shutting down " wht("NOW"));
|
|
#endif
|
|
|
|
do_poweroff();
|
|
}
|
|
|
|
static void do_poweroff(void)
|
|
{
|
|
execl(POWEROFF_PATH, POWEROFF_PATH, NULL);
|
|
perror_and_exit(2, "exec() failed");
|
|
}
|
|
|
|
static void perror_and_exit(int status, const char *message)
|
|
{
|
|
if (!message)
|
|
message = "poweroff_guard";
|
|
perror(message);
|
|
exit(status);
|
|
}
|
|
|
|
/*
|
|
* LICENSE TERMS
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|