From 6053a358dd187fd56bdd1753fbc6b8285c35cd98 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 13 May 2019 14:43:35 +0200 Subject: Add reboot/kexec/poweroff via RPC --- src/main.c | 102 ++++++++++++--------- src/main.h | 16 ++++ src/rpc.c | 304 +++++++++++++++++++++++++++++++++++++++++-------------------- src/rpc.h | 4 + 4 files changed, 286 insertions(+), 140 deletions(-) diff --git a/src/main.c b/src/main.c index f9960f5..032aa27 100644 --- a/src/main.c +++ b/src/main.c @@ -25,13 +25,6 @@ static time_t lastActivity; static struct user *users; // List of reboot and shutdown times -enum Shutdown { - REBOOT = 0, - POWEROFF, - KEXEC, - SUSPEND, - SHUTDOWN_ENUM_END, -}; static const char *shutdownActions[SHUTDOWN_ENUM_END] = { [REBOOT] = "reboot", [POWEROFF] = "poweroff", @@ -39,11 +32,6 @@ static const char *shutdownActions[SHUTDOWN_ENUM_END] = { [SUSPEND] = "suspend", }; #define TABMAX (20) -struct time { - int hour; - int minute; - enum Shutdown action; -}; static struct { time_t deadline; // MONOTONIC; 0 = none, or last one was disarmed and time elapsed past deadline enum Shutdown action; @@ -63,6 +51,8 @@ static struct { int gracePeriod; } config; +static time_t combineTime( const time_t now, const struct time * time ); + static void getXAuthority( struct user *user ); static bool parseCmdline( int argc, char **argv ); @@ -216,37 +206,22 @@ int main( int argc, char **argv ) nextAction.deadline = 0; } const time_t NOW = time( NULL ); - struct tm tmNOW; - if ( localtime_r( &NOW, &tmNOW ) != NULL ) { - struct tm dtime; - time_t deadline; - int delta; - for ( idx = 0; idx < config.shutdowns; ++idx ) { - dtime = tmNOW; - dtime.tm_hour = config.shutdown[idx].hour; - dtime.tm_min = config.shutdown[idx].minute; - dtime.tm_sec = 0; - deadline = mktime( &dtime ); - if ( deadline + 6 < NOW ) { - // Try again tomorrow - dtime.tm_mday++; - deadline = mktime( &dtime ); - } - if ( deadline == -1 ) { - perror( "Cannot convert shutdown entry to time_t via mktime" ); - continue; - } - delta = deadline - NOW; - if ( delta >= -6 && ( nextAction.deadline == 0 || ( nextAction.deadline - monoNOW - 3 ) > delta ) ) { - // Engage - nextAction.deadline = monoNOW + ( delta < 0 ? 0 : delta ); - nextAction.disarmed = false; - nextAction.action = config.shutdown[idx].action; - if ( delta < 300 ) { - printf( "Scheduling reboot/poweroff in %d seconds\n", delta ); - } - CAP_SLEEP( delta ); + time_t deadline; + int delta; + for ( idx = 0; idx < config.shutdowns; ++idx ) { + deadline = combineTime( NOW, &config.shutdown[idx] ); + if ( deadline == 0 ) + continue; + delta = deadline - NOW; + if ( delta >= -6 && ( nextAction.deadline == 0 || ( nextAction.deadline - monoNOW - 3 ) > delta ) ) { + // Engage + nextAction.deadline = monoNOW + ( delta < 0 ? 0 : delta ); + nextAction.disarmed = false; + nextAction.action = config.shutdown[idx].action; + if ( delta < 300 ) { + printf( "Scheduling reboot/poweroff in %d seconds\n", delta ); } + CAP_SLEEP( delta ); } } } @@ -324,6 +299,32 @@ int main( int argc, char **argv ) return 0; } +static time_t combineTime( const time_t now, const struct time * time ) +{ + struct tm dtime; + if ( localtime_r( &now, &dtime ) == NULL ) + return 0; + time_t result; + dtime.tm_hour = time->hour; + dtime.tm_min = time->minute; + dtime.tm_sec = 0; + result = mktime( &dtime ); + if ( result + 6 < now ) { + // Try again tomorrow + dtime.tm_mday++; + result = mktime( &dtime ); + } + if ( result == -1 ) { + perror( "Cannot convert struct tm to time_t via mktime" ); + return 0; + } + if ( result < now ) { + fprintf( stderr, "combineTime: Even +1 day seems in the past!?\n" ); + return 0; + } + return result; +} + /** * This doesn't really use any clever logic but rather assumes that * it will be $HOME/.Xauthority @@ -359,6 +360,7 @@ static struct option long_options[] = { { "dpms-timeout", required_argument, NULL, 'dpms' }, { "grace-period", required_argument, NULL, 'gp' }, { "cmd", required_argument, NULL, 'cmd' }, + { "send", required_argument, NULL, 'send' }, { "test", no_argument, NULL, 't' }, { NULL, 0, NULL, 0 } }; @@ -435,6 +437,8 @@ static bool parseCmdline( int argc, char **argv ) case 'cmd': config.shutdownCommand = strdup( optarg ); break; + case 'send': + exit( !rpc_send( optarg ) ); default: fprintf( stderr, "Unhandled command line option %d, aborting\n", ch ); return false; @@ -496,3 +500,17 @@ struct user* main_getUser( const char *terminal ) return NULL; } +void main_queueAction( enum Shutdown action, int delta ) +{ + if ( delta < 0 || action < 0 || action >= SHUTDOWN_ENUM_END ) + return; + time_t monoNOW = now(); + if ( nextAction.deadline == 0 || ( nextAction.deadline - monoNOW - 3 ) > delta ) { + // Engage + nextAction.deadline = monoNOW + delta; + nextAction.disarmed = false; + nextAction.action = action; + printf( "RPC: Scheduling reboot/poweroff in %d seconds\n", delta ); + } +} + diff --git a/src/main.h b/src/main.h index 1ff924a..2c792ac 100644 --- a/src/main.h +++ b/src/main.h @@ -3,8 +3,24 @@ #include +enum Shutdown { + REBOOT = 0, + POWEROFF, + KEXEC, + SUSPEND, + SHUTDOWN_ENUM_END, +}; + +struct time { + int hour; + int minute; + enum Shutdown action; +}; + void main_getStatus( const char **nextAction, time_t *deadline ); struct user* main_getUser( const char *terminal ); +void main_queueAction( enum Shutdown action, int seconds ); + #endif diff --git a/src/rpc.c b/src/rpc.c index e49547e..bfcc2c3 100644 --- a/src/rpc.c +++ b/src/rpc.c @@ -4,7 +4,7 @@ #include "userlist.h" #include -#include +#include #include #include #include @@ -14,119 +14,227 @@ #include #include +#define SENDSTRING(fd,x) write( fd, x, strlen(x) ) + #define SOCKPATH "/run/idle-daemon" +#pragma pack(1) +static struct { + int read; + int write; +} rpcPipe = { + .read = -1, + .write = -1, +}; +#pragma pack(0) + +_Static_assert( sizeof(rpcPipe) == ( sizeof(int) * 2 ), "Structsize mismatch" ); + static void handleClient( int fd, struct ucred *user ); int rpcOpen() { - int fd = socket( AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0 ); - if ( fd == -1 ) { - perror( "Cannot create local RPC socket" ); - return -1; - } - struct sockaddr_un address = { - .sun_family = AF_UNIX, - .sun_path = SOCKPATH, - }; - unlink( SOCKPATH ); - if ( bind( fd, (struct sockaddr *)&address, sizeof(address) ) == -1 ) { - perror( "Could not bind RPC socket" ); - close( fd ); - return -1; - } - chmod( SOCKPATH, 0777 ); - if ( listen( fd, 10 ) == -1 ) { - perror( "Could not listen() on RPC socket" ); - close( fd ); - return -1; - } - return fd; + int fd = socket( AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0 ); + if ( fd == -1 ) { + perror( "Cannot create local RPC socket" ); + return -1; + } + struct sockaddr_un address = { + .sun_family = AF_UNIX, + .sun_path = SOCKPATH, + }; + unlink( SOCKPATH ); + if ( bind( fd, (struct sockaddr *)&address, sizeof(address) ) == -1 ) { + perror( "Could not bind RPC socket" ); + close( fd ); + return -1; + } + chmod( SOCKPATH, 0777 ); + if ( listen( fd, 10 ) == -1 ) { + perror( "Could not listen() on RPC socket" ); + close( fd ); + return -1; + } + if ( rpcPipe.write == -1 && rpcPipe.read == -1 ) { + if ( pipe2( (int*)&rpcPipe, O_DIRECT ) == -1 ) { + perror( "Cannot create local RPC pipe" ); + } else { + // Read end nonblocking + int flags = fcntl( rpcPipe.read, F_GETFL, 0 ); + fcntl( rpcPipe.read, F_SETFL, flags | O_NONBLOCK ); + } + } + return fd; } void rpcWait( int listenFd, int seconds ) { - waitRead( listenFd, seconds * 1000 ); + struct pollfd pd[2] = { + { .fd = listenFd, .events = POLLIN | POLLHUP | POLLRDHUP }, + { .fd = rpcPipe.read, .events = POLLIN | POLLHUP | POLLRDHUP }, + }; + if ( poll( pd, 2, seconds * 1000 ) == -1 ) { + perror( "Error polling RPC sockets" ); + } } void rpcHandle( int listenFd ) { - int fd; - while ( ( fd = accept( listenFd, NULL, NULL ) ) != -1 ) { - // Determine who connected - socklen_t len; - struct ucred ucred; - len = sizeof(struct ucred); - if ( getsockopt( fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len ) == -1 ) { - perror( "Could not get credentials of connection" ); - close( fd ); // TODO: Allow more for root (eg. cancel reboot/poweroff) - continue; - } - if ( ! doublefork() ) - continue; // Parent continues - handleClient( fd, &ucred ); - exit( 0 ); - } - if ( errno != EAGAIN && errno != EWOULDBLOCK ) { - perror( "accept() on RPC socket failed" ); - } + int fd; + while ( ( fd = accept( listenFd, NULL, NULL ) ) != -1 ) { + // Determine who connected + socklen_t len; + struct ucred ucred; + len = sizeof(struct ucred); + if ( getsockopt( fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len ) == -1 ) { + perror( "Could not get credentials of connection" ); + close( fd ); // TODO: Allow more for root (eg. cancel reboot/poweroff) + continue; + } + if ( ! doublefork() ) + continue; // Parent continues + handleClient( fd, &ucred ); + exit( 0 ); + } + if ( errno != EAGAIN && errno != EWOULDBLOCK ) { + perror( "accept() on RPC socket failed" ); + } + // Check if any RPC client talks to us + char buffer[200]; + ssize_t len; + while ( ( len = read( rpcPipe.read, buffer, sizeof(buffer) - 1 ) ) > 0 ) { + buffer[len] = '\0'; + const char *ptr = NULL; + enum Shutdown action = SHUTDOWN_ENUM_END; + if ( strncmp( buffer, "reboot ", 7 ) == 0 ) { + ptr = buffer + 7; + action = REBOOT; + } else if ( strncmp( buffer, "poweroff ", 9 ) == 0 ) { + ptr = buffer + 9; + action = POWEROFF; + } else if ( strncmp( buffer, "kexec ", 6 ) == 0 ) { + ptr = buffer + 6; + action = KEXEC; + } else { + fprintf( stderr, "Unknown RPC pipe callback: %s\n", buffer ); + } + if ( ptr != NULL ) { + char *end; + int seconds = strtol( ptr, &end, 10 ); + if ( end != ptr && seconds >= 0 ) { + main_queueAction( action, seconds ); + } else { + fprintf( stderr, "RPC: Ignoring reboot/poweroff with invalid timeout\n" ); + } + } + } + if ( len == -1 && errno != EAGAIN && errno != EWOULDBLOCK ) { + perror( "Error reading vom RPC local pipe" ); + } +} + +static void handleClient( int fd, struct ucred *creds ) +{ + //printf( "Credentials from SO_PEERCRED: pid=%ld, euid=%ld, egid=%ld\n", + // (long) ucred.pid, (long) ucred.uid, (long) ucred.gid ); + // Make socket blocking (should be default on linux after accept() but...) + int flags = fcntl( fd, F_GETFL, 0 ); + if ( flags != -1 ) { + fcntl( fd, F_SETFL, flags & ~O_NONBLOCK ); + } + // But set timeouts + struct timeval tv = { + .tv_sec = 2, + }; + setsockopt( fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv) ); + setsockopt( fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv) ); + // Now read request + char buffer[1000]; + ssize_t len = read( fd, buffer, sizeof(buffer) - 1 ); + if ( len <= 0 ) + return; + buffer[len] = '\0'; + if ( strncmp( buffer, "get", 3 ) == 0 ) { + // Get request + FILE *s = fdopen( fd, "w" ); + if ( s == NULL ) { + perror( "Cannot wrap socket in stream" ); + return; + } + // 1) Global state + time_t deadline; + const char *name = "none"; + main_getStatus( &name, &deadline ); + fprintf( s, "[General]\n" + "nextAction=%s\n" + "nextActionTime=%lld\n", + name, (long long)deadline ); + // 2) Requested sessions + char *tok = strtok( buffer + 4, " \t\n\r" ); + while ( tok != NULL ) { + struct user *user = main_getUser( tok ); + if ( user != NULL ) { + fprintf( s, "[%s]\n" + "logoutTime=%lld\n" + "locked=%d\n", + tok, (long long)user->logoutTime, (int)user->isLocked ); + } + tok = strtok( NULL, " \t\n\r" ); + } + fflush( s ); + // Don't fclose since we need to shutdown below. This is a leak, but since we're + // running in a child that will exit soon, just close your eyes for a second... + } else if ( strncmp( buffer, "reboot ", 7 ) == 0 || strncmp( buffer, "poweroff ", 9 ) == 0 + || strncmp( buffer, "kexec ", 6 ) == 0 ) { + if ( creds->uid != 0 ) { + SENDSTRING( fd, "error only root can do this" ); + } else { + if ( write( rpcPipe.write, buffer, len ) == -1 ) { + perror( "RPC Child: Cannot write to parent pipe" ); + SENDSTRING( fd, "error cannot write to parent" ); + } else { + SENDSTRING( fd, "ok" ); + } + } + } else { + SENDSTRING( fd, "error unknown command" ); + } + shutdown( fd, SHUT_WR ); + read( fd, buffer, sizeof(buffer) ); + close( fd ); } -static void handleClient( int fd, struct ucred *user ) +bool rpc_send( const char *data ) { - //printf( "Credentials from SO_PEERCRED: pid=%ld, euid=%ld, egid=%ld\n", - // (long) ucred.pid, (long) ucred.uid, (long) ucred.gid ); - // Make socket blocking (should be default on linux after accept() but...) - (void)user; - int flags = fcntl( fd, F_GETFL, 0 ); - if ( flags != -1 ) { - fcntl( fd, F_SETFL, flags & ~O_NONBLOCK ); - } - // But set timeouts - struct timeval tv = { - .tv_sec = 2, - }; - setsockopt( fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv) ); - setsockopt( fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv) ); - // Now read request - char buffer[1000]; - ssize_t len = read( fd, buffer, sizeof(buffer) - 1 ); - if ( len <= 0 ) - return; - buffer[len] = '\0'; - if ( strncmp( buffer, "get", 3 ) != 0 ) { - write( fd, "error", 5 ); - } else { - // Get request - FILE *s = fdopen( fd, "w" ); - if ( s == NULL ) { - perror( "Cannot wrap socket in stream" ); - return; - } - // 1) Global state - time_t deadline; - const char *name = "none"; - main_getStatus( &name, &deadline ); - fprintf( s, "[General]\n" - "nextAction=%s\n" - "nextActionTime=%lld\n", - name, (long long)deadline ); - // 2) Requested sessions - char *tok = strtok( buffer + 4, " \t\n\r" ); - while ( tok != NULL ) { - struct user *user = main_getUser( tok ); - if ( user != NULL ) { - fprintf( s, "[%s]\n" - "logoutTime=%lld\n" - "locked=%d\n", - tok, (long long)user->logoutTime, (int)user->isLocked ); - } - tok = strtok( NULL, " \t\n\r" ); - } - fflush( s ); - } - shutdown( fd, SHUT_WR ); - read( fd, buffer, sizeof(buffer) ); - close( fd ); + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + .sun_path = SOCKPATH, + }; + int fd = socket( AF_UNIX, SOCK_STREAM, 0 ); + if ( fd == -1 ) { + perror( "RPC Client: socket() failed" ); + return false; + } + if ( connect( fd, &addr, sizeof(addr) ) == -1 ) { + perror( "RPC Client: connect() failed" ); + close( fd ); + return false; + } + if ( write( fd, data, strlen( data ) ) <= 0 ) { + perror( "RPC Client: write() failed" ); + close( fd ); + return false; + } + shutdown( fd, SHUT_WR ); + char buffer[200]; + ssize_t len; + if ( ( len = read( fd, buffer, sizeof(buffer) - 1 ) ) == -1 ) { + perror( "RPC Client: read()ing reply failed" ); + } else { + buffer[len] = '\0'; + printf( "%s\n", buffer ); + } + close( fd ); + return true; } diff --git a/src/rpc.h b/src/rpc.h index 08e08cd..e6fb353 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -1,10 +1,14 @@ #ifndef _RPC_H__ #define _RPC_H__ +#include + int rpcOpen(); void rpcHandle( int listenFd ); void rpcWait( int listenFd, int seconds ); +bool rpc_send( const char *data ); + #endif -- cgit v1.2.3-55-g7522