summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2019-05-13 14:43:35 +0200
committerSimon Rettberg2019-05-13 14:43:35 +0200
commit6053a358dd187fd56bdd1753fbc6b8285c35cd98 (patch)
treefd263b39de63d6c3bd4dc058fcddeef2a1003d64
parentInitial commit (diff)
downloadidle-daemon-6053a358dd187fd56bdd1753fbc6b8285c35cd98.tar.gz
idle-daemon-6053a358dd187fd56bdd1753fbc6b8285c35cd98.tar.xz
idle-daemon-6053a358dd187fd56bdd1753fbc6b8285c35cd98.zip
Add reboot/kexec/poweroff via RPC
-rw-r--r--src/main.c102
-rw-r--r--src/main.h16
-rw-r--r--src/rpc.c304
-rw-r--r--src/rpc.h4
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 <time.h>
+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 <poll.h>
-#include <sys/types.h>
+#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
@@ -14,119 +14,227 @@
#include <unistd.h>
#include <stdlib.h>
+#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 <stdbool.h>
+
int rpcOpen();
void rpcHandle( int listenFd );
void rpcWait( int listenFd, int seconds );
+bool rpc_send( const char *data );
+
#endif