greatly simplify it to report idle seconds to a script
This commit is contained in:
parent
acf64db071
commit
70b3e7eaa6
24
README
24
README
@ -1,4 +1,24 @@
|
|||||||
afkcron
|
afkcron
|
||||||
=======
|
=======
|
||||||
afkcron reads a configuration which specifies programs to launch after n seconds of afk time.
|
afkcron simply reports the idle time of the X11 server to a shell script.
|
||||||
Once the user is afk for those seconds, it then starts the program.
|
|
||||||
|
Originally, it was reading config files and also handling the return of the
|
||||||
|
user by decreasing priority of launched processes or killing them etc.
|
||||||
|
|
||||||
|
Now I considered it's best for it to be rather stupid and delegate this
|
||||||
|
to a shell script for example.
|
||||||
|
|
||||||
|
Launching afkcron
|
||||||
|
=================
|
||||||
|
afkcron [path to script] [how often idle time should be polled, in seconds]
|
||||||
|
|
||||||
|
Example: afkcron script.sh 30
|
||||||
|
|
||||||
|
Shellscript
|
||||||
|
===========
|
||||||
|
First argument: "active" (if idle time has stopped), "idle": When idling
|
||||||
|
Second argument: the number of seconds X11 is idling.
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
============
|
||||||
|
libX11, libXss. Should be a given.
|
||||||
|
447
afkcron.c
447
afkcron.c
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* afkcron launches a program depending on the time the user is not using his machine
|
* afkcron launches a script depending on the time the user is not using his machine
|
||||||
*
|
*
|
||||||
* Copyright (c) 2014-2017 Albert S. <launchutils at quitesimple dot org>
|
* Copyright (c) 2014-2018 Albert S. <launchutils at quitesimple dot org>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
@ -19,137 +19,17 @@
|
|||||||
#define _XOPEN_SOURCE 700
|
#define _XOPEN_SOURCE 700
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdarg.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <sys/resource.h>
|
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#include <X11/extensions/scrnsaver.h>
|
#include <X11/extensions/scrnsaver.h>
|
||||||
#include <ctype.h>
|
|
||||||
|
|
||||||
#define CONFIG_DEFAULT_PATH "/etc/afkcron"
|
|
||||||
#define CONFIG_DELIMER ':'
|
|
||||||
|
|
||||||
//return actions flags
|
|
||||||
#define FLAG_RETURN_KILL (1 << 0)
|
|
||||||
#define FLAG_RETURN_PRIO (1 << 1)
|
|
||||||
#define FLAG_RETURN_STOP (1 << 2)
|
|
||||||
#define FLAG_RETURN_STAY (1 << 3)
|
|
||||||
#define FLAG_RETURN_TERM (1 << 4)
|
|
||||||
#define FLAG_RETURN_TERMKILL (FLAG_RETURN_TERM | FLAG_RETURN_KILL)
|
|
||||||
//general flags
|
|
||||||
#define FLAG_SINGLE_SHOT (1 << 0) // fire only once during program lifetime
|
|
||||||
|
|
||||||
//consider merging return action flags and general flags
|
|
||||||
struct entry
|
|
||||||
{
|
|
||||||
pid_t pid; // 0 = can be ran, -1 single shot entry which finished, pid > 0 = currently running
|
|
||||||
bool stopped;
|
|
||||||
const char *path;
|
|
||||||
const char *args;
|
|
||||||
int comeback_action; //What to do, when the user returns, starts using the computer again?
|
|
||||||
int idle_seconds;
|
|
||||||
int flags;
|
|
||||||
struct entry *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
FILE *logfp = NULL;
|
|
||||||
|
|
||||||
struct entry *head_entry = NULL;
|
|
||||||
struct entry *tail_entry = NULL;
|
|
||||||
|
|
||||||
void *xmalloc(size_t n)
|
|
||||||
{
|
|
||||||
char *tmp = malloc(n);
|
|
||||||
if(tmp == NULL)
|
|
||||||
{
|
|
||||||
perror("malloc");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *xstrdup(const char *str)
|
|
||||||
{
|
|
||||||
char *result = strdup(str);
|
|
||||||
if(result == NULL)
|
|
||||||
{
|
|
||||||
perror("strdup");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strempty(const char *str)
|
bool strempty(const char *str)
|
||||||
{
|
{
|
||||||
return str == NULL || *str == '\0';
|
return str == NULL || *str == '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
int strcnt(const char *str, const char c)
|
|
||||||
{
|
|
||||||
int result = 0;
|
|
||||||
while(*str)
|
|
||||||
if(*str++ == c) ++result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int split_string(char ***out, const char *str, char delim)
|
|
||||||
{
|
|
||||||
int current = 0;
|
|
||||||
int items = strcnt(str, delim) + 1;
|
|
||||||
*out = xmalloc(items * sizeof(char *));
|
|
||||||
char *temp = xstrdup(str);
|
|
||||||
char *portion = temp;
|
|
||||||
while(*temp)
|
|
||||||
{
|
|
||||||
if(*temp == delim)
|
|
||||||
{
|
|
||||||
*temp=0;
|
|
||||||
(*out)[current] = portion;
|
|
||||||
++current;
|
|
||||||
portion = temp+1;
|
|
||||||
}
|
|
||||||
++temp;
|
|
||||||
}
|
|
||||||
(*out)[current] = portion;
|
|
||||||
return ++current;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char **create_execv_args(const char *name, const char *str, char delim)
|
|
||||||
{
|
|
||||||
if(strempty(name))
|
|
||||||
return NULL;
|
|
||||||
const char **result = NULL;
|
|
||||||
if(strempty(str))
|
|
||||||
{
|
|
||||||
result = xmalloc(2 * sizeof(char *));
|
|
||||||
result[0] = name;
|
|
||||||
result[1] = NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int items=2;
|
|
||||||
char **args = NULL;
|
|
||||||
int n_args = split_string(&args, str, delim);
|
|
||||||
items += n_args;
|
|
||||||
result = xmalloc(items * sizeof(char *));
|
|
||||||
result[0] = name;
|
|
||||||
for(int i=0; i < n_args; i++)
|
|
||||||
result[i+1] = args[i];
|
|
||||||
result[items-1] = NULL;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void print_err_and_exit(const char *s)
|
void print_err_and_exit(const char *s)
|
||||||
{
|
{
|
||||||
fputs(s, stderr);
|
fputs(s, stderr);
|
||||||
@ -163,335 +43,116 @@ void perror_and_exit(const char *s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void logit(const char *format, ...)
|
|
||||||
{
|
|
||||||
if(logfp != NULL)
|
|
||||||
{
|
|
||||||
va_list args;
|
|
||||||
va_start(args, format);
|
|
||||||
time_t now = time(0);
|
|
||||||
char *timestr = ctime(&now);
|
|
||||||
size_t len = strlen(timestr);
|
|
||||||
if(timestr[len-1] == '\n')
|
|
||||||
{
|
|
||||||
timestr[len-1] = 0;
|
|
||||||
}
|
|
||||||
fprintf(logfp, "%s: ", timestr);
|
|
||||||
vfprintf(logfp, format, args);
|
|
||||||
fflush(logfp);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int get_idle_seconds(Display *display)
|
int get_idle_seconds(Display *display)
|
||||||
{
|
{
|
||||||
XScreenSaverInfo info;
|
XScreenSaverInfo info;
|
||||||
if(XScreenSaverQueryInfo(display, DefaultRootWindow(display), &info) == 0)
|
if(XScreenSaverQueryInfo(display, DefaultRootWindow(display), &info) == 0)
|
||||||
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
return info.idle / 1000;
|
return info.idle / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_entry(struct entry *e)
|
|
||||||
{
|
|
||||||
if(head_entry == NULL)
|
|
||||||
head_entry = e;
|
|
||||||
if(tail_entry != NULL)
|
|
||||||
tail_entry->next = e;
|
|
||||||
tail_entry = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int comeback_action_from_string(const char *str)
|
|
||||||
{
|
|
||||||
int result = 0;
|
|
||||||
if(! strempty(str))
|
|
||||||
{
|
|
||||||
if(strcmp(str, "kill") == 0)
|
|
||||||
result |= FLAG_RETURN_KILL;
|
|
||||||
else if(strcmp(str, "stop") == 0)
|
|
||||||
result |= FLAG_RETURN_STOP;
|
|
||||||
else if(strcmp(str, "prio") == 0)
|
|
||||||
result |= FLAG_RETURN_PRIO;
|
|
||||||
else if(strcmp(str, "term") == 0)
|
|
||||||
result |= FLAG_RETURN_TERM;
|
|
||||||
else if(strcmp(str, "termkill") == 0)
|
|
||||||
result |= FLAG_RETURN_TERMKILL;
|
|
||||||
else
|
|
||||||
result |= FLAG_RETURN_STAY;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int flags_from_string(const char *str)
|
|
||||||
{
|
|
||||||
int result = 0;
|
|
||||||
if(strstr(str, "oneshot"))
|
|
||||||
result |= FLAG_SINGLE_SHOT;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
//TODO: check integer overflow
|
|
||||||
int secs_from_string(char *unitstr)
|
|
||||||
{
|
|
||||||
if(unitstr == NULL)
|
|
||||||
return -1;
|
|
||||||
size_t len = strlen(unitstr);
|
|
||||||
char unit = unitstr[len-1];
|
|
||||||
if(! isdigit(unit))
|
|
||||||
unitstr[len-1] = '\0';
|
|
||||||
|
|
||||||
int secs = atoi(unitstr);
|
|
||||||
if(secs < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
switch(unit)
|
|
||||||
{
|
|
||||||
case 'd':
|
|
||||||
secs *= 24;
|
|
||||||
case 'h':
|
|
||||||
secs *= 60;
|
|
||||||
case 'm':
|
|
||||||
secs *= 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
return secs;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct entry *entry_from_line(const char *line)
|
|
||||||
{
|
|
||||||
char *l = xstrdup(line);
|
|
||||||
|
|
||||||
char **fields = NULL;
|
|
||||||
int n_fields = split_string(&fields, l, CONFIG_DELIMER);
|
|
||||||
if(n_fields < 5)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
struct entry *result = xmalloc(sizeof(struct entry));
|
|
||||||
result->path = fields[0];
|
|
||||||
result->args = fields[1];
|
|
||||||
result->comeback_action = comeback_action_from_string(fields[2]);
|
|
||||||
result->idle_seconds = secs_from_string(fields[3]);
|
|
||||||
result->flags = flags_from_string(fields[4]);
|
|
||||||
return result;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static inline bool check_entry(struct entry *e)
|
|
||||||
{
|
|
||||||
return ( ! strempty(e->path) ) && ( e->idle_seconds > 0 ) && ( e->comeback_action > 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
void read_config(const char *configfile)
|
|
||||||
{
|
|
||||||
FILE *fp = fopen(configfile, "r");
|
|
||||||
if(fp == NULL)
|
|
||||||
{
|
|
||||||
perror_and_exit("fopen");
|
|
||||||
}
|
|
||||||
char *line;
|
|
||||||
size_t n = 0;
|
|
||||||
ssize_t r;
|
|
||||||
while((r = getline(&line, &n, fp)) != -1 )
|
|
||||||
{
|
|
||||||
if(line[r-1] == '\n')
|
|
||||||
line[r-1] = '\0';
|
|
||||||
|
|
||||||
struct entry *e = entry_from_line(line);
|
|
||||||
if(e == NULL)
|
|
||||||
print_err_and_exit("error reading from file");
|
|
||||||
|
|
||||||
if(! check_entry(e))
|
|
||||||
print_err_and_exit("Invalid values for entry");
|
|
||||||
|
|
||||||
add_entry(e);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ferror(fp))
|
|
||||||
perror_and_exit("error reading from config file");
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_comeback()
|
|
||||||
{
|
|
||||||
for(struct entry *current = head_entry; current != NULL; current = current->next)
|
|
||||||
{
|
|
||||||
if(current->pid > 0)
|
|
||||||
{
|
|
||||||
int comeback_action = current->comeback_action;
|
|
||||||
bool wants_term = (comeback_action & FLAG_RETURN_TERM) == FLAG_RETURN_TERM;
|
|
||||||
|
|
||||||
if(wants_term)
|
|
||||||
{
|
|
||||||
logit("Terminating %z\n", current->pid);
|
|
||||||
kill(current->pid, SIGTERM);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(comeback_action & FLAG_RETURN_KILL)
|
|
||||||
{
|
|
||||||
if(wants_term)
|
|
||||||
while(sleep(2));
|
|
||||||
if(kill(current->pid, SIGKILL) == 0)
|
|
||||||
{
|
|
||||||
logit("Killed %z\n", current->pid);
|
|
||||||
current->pid = 0;
|
|
||||||
}
|
|
||||||
//TODO: and if fail?...
|
|
||||||
}
|
|
||||||
if(comeback_action & FLAG_RETURN_STOP)
|
|
||||||
{
|
|
||||||
if(kill(current->pid, SIGSTOP) == 0)
|
|
||||||
{
|
|
||||||
logit("Stopped %z\n", current->pid);
|
|
||||||
current->stopped = true;
|
|
||||||
//TODO: ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(comeback_action & FLAG_RETURN_PRIO)
|
|
||||||
{
|
|
||||||
logit("Lowering priority for %z\n", current->pid);
|
|
||||||
setpriority(PRIO_PROCESS, current->pid, 19);
|
|
||||||
//TODO: IO prio?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_finished_pid(pid_t pid)
|
|
||||||
{
|
|
||||||
for(struct entry *current = head_entry; current != NULL; current = current->next)
|
|
||||||
{
|
|
||||||
if(current->pid == pid)
|
|
||||||
{
|
|
||||||
if( (current->flags & FLAG_SINGLE_SHOT) )
|
|
||||||
{
|
|
||||||
current->pid = -1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
current->pid = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void child_handler(int signum, siginfo_t *info, void *context)
|
void child_handler(int signum, siginfo_t *info, void *context)
|
||||||
{
|
{
|
||||||
if(signum != SIGCHLD)
|
if(signum != SIGCHLD)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
pid_t pid = info->si_pid;
|
|
||||||
//TODO
|
|
||||||
int status;
|
int status;
|
||||||
int x = waitpid(pid, &status, WNOHANG);
|
int ret = waitpid(info->si_pid, &status, WNOHANG);
|
||||||
if(x == -1)
|
if(ret == -1)
|
||||||
print_err_and_exit("waitpid failed");
|
{
|
||||||
handle_finished_pid(pid);
|
perror_and_exit("waitpid failed");
|
||||||
|
}
|
||||||
|
if(status == EXIT_FAILURE)
|
||||||
|
{
|
||||||
|
print_err_and_exit("script did not succeed, exiting");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_signals()
|
void set_signals()
|
||||||
{
|
{
|
||||||
struct sigaction action;
|
struct sigaction action;
|
||||||
action.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
|
action.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
|
||||||
action.sa_sigaction = &child_handler;
|
action.sa_sigaction = &child_handler;
|
||||||
if(sigaction(SIGCHLD, &action, NULL) == -1)
|
if(sigaction(SIGCHLD, &action, NULL) == -1)
|
||||||
print_err_and_exit("sigaction failed");
|
{
|
||||||
|
perror_and_exit("sigaction failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void run_script(const char *path, const char *type, int sleepseconds)
|
||||||
bool run_entry(struct entry *e)
|
|
||||||
{
|
{
|
||||||
const char **args = create_execv_args(e->path, e->args, ' ');
|
|
||||||
pid_t child = fork();
|
pid_t child = fork();
|
||||||
if(child == 0)
|
if(child == 0)
|
||||||
{
|
{
|
||||||
logit("Starting execution of %s\n", e->path);
|
char sleepstr[20];
|
||||||
execv(e->path, (char * const *)args);
|
snprintf(sleepstr, sizeof(sleepstr), "%i", sleepseconds);
|
||||||
fclose(logfp);
|
const char *args[4];
|
||||||
}
|
args[0] = path;
|
||||||
else if(child > 0)
|
args[1] = type;
|
||||||
e->pid = child;
|
args[2] = sleepstr;
|
||||||
return child != -1;
|
args[3] = NULL;
|
||||||
}
|
execv(path, (char * const *)args);
|
||||||
|
perror_and_exit("failed launching script");
|
||||||
|
|
||||||
void run_entries(int idle_seconds)
|
}
|
||||||
{
|
if(child == -1)
|
||||||
for(struct entry *e = head_entry; e != NULL; e = e->next)
|
|
||||||
{
|
{
|
||||||
if(e->idle_seconds > idle_seconds)
|
perror_and_exit("fork failed");
|
||||||
continue;
|
|
||||||
if(e->pid == 0)
|
|
||||||
run_entry(e);
|
|
||||||
if(e->pid > 0 && e->stopped)
|
|
||||||
{
|
|
||||||
logit("Continuing %z\n", e->pid);
|
|
||||||
if(kill(e->pid, SIGCONT) == 0)
|
|
||||||
e->stopped = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
if(argc < 3)
|
||||||
|
{
|
||||||
|
printf("Usage: afkcron script.sh interval\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *scriptpath = argv[1];
|
||||||
|
unsigned int interval = strtol(argv[2], NULL, 10);
|
||||||
|
|
||||||
Display *display = XOpenDisplay(NULL);
|
Display *display = XOpenDisplay(NULL);
|
||||||
if(display == NULL)
|
if(display == NULL)
|
||||||
|
{
|
||||||
print_err_and_exit("Couldn't open DISPLAY");
|
print_err_and_exit("Couldn't open DISPLAY");
|
||||||
|
}
|
||||||
|
|
||||||
int event_base, error_base;
|
int event_base, error_base;
|
||||||
|
|
||||||
if (! XScreenSaverQueryExtension(display, &event_base, &error_base))
|
if (! XScreenSaverQueryExtension(display, &event_base, &error_base))
|
||||||
|
{
|
||||||
print_err_and_exit("No XScreenSaver Extension available on this display");
|
print_err_and_exit("No XScreenSaver Extension available on this display");
|
||||||
|
}
|
||||||
set_signals();
|
set_signals();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int option;
|
|
||||||
while((option = getopt(argc, argv, "c:l:")) != -1)
|
|
||||||
{
|
|
||||||
switch(option)
|
|
||||||
{
|
|
||||||
case 'c':
|
|
||||||
read_config(optarg);
|
|
||||||
break;
|
|
||||||
case 'l':
|
|
||||||
logfp = fopen(optarg, "a");
|
|
||||||
if(logfp == NULL)
|
|
||||||
{
|
|
||||||
print_err_and_exit("Error opening log file");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(head_entry == NULL)
|
|
||||||
{
|
|
||||||
read_config(CONFIG_DEFAULT_PATH);
|
|
||||||
}
|
|
||||||
int previous_seconds = 0;
|
int previous_seconds = 0;
|
||||||
while(1)
|
while(1)
|
||||||
{
|
{
|
||||||
int idle_seconds = get_idle_seconds(display);
|
int idle_seconds = get_idle_seconds(display);
|
||||||
if(idle_seconds == -1)
|
if(idle_seconds == -1)
|
||||||
|
{
|
||||||
print_err_and_exit("X11 Screen Saver Extension not supported?");
|
print_err_and_exit("X11 Screen Saver Extension not supported?");
|
||||||
|
}
|
||||||
|
|
||||||
if(previous_seconds > idle_seconds)
|
if(previous_seconds > idle_seconds)
|
||||||
handle_comeback();
|
{
|
||||||
|
run_script(scriptpath, "active", idle_seconds);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
run_entries(idle_seconds);
|
{
|
||||||
|
run_script(scriptpath, "idle", idle_seconds);
|
||||||
|
}
|
||||||
previous_seconds = idle_seconds;
|
previous_seconds = idle_seconds;
|
||||||
while(sleep(10)); // Well this sleep approach is suboptimal, do it with events somehow if possible.
|
while(sleep(interval));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
/bin/touch:/tmp/works:kill:5:oneshot
|
|
Loading…
Reference in New Issue
Block a user