12 次代码提交
v1.2 ... master

作者 SHA1 备注 提交日期
4b467fa963 update Makefile 2025-11-08 16:13:52 +01:00
1ff23bd203 update README 2025-11-08 16:05:28 +01:00
569e5afd71 update README 2025-11-08 15:16:14 +01:00
41b803df43 Warn if -w not provided 2025-11-08 15:12:40 +01:00
50af69c659 Exit on unknown options 2025-11-08 15:12:10 +01:00
5f3e73e592 Retire file_exists() 2025-10-01 14:05:22 +02:00
0d5e6cd052 Add option: -V --version 2025-10-01 14:03:39 +02:00
53f8aa343b mask_to_names(): Handle if ftell() is not successful 2025-09-30 18:59:04 +02:00
9a5e96f058 Update move_downloads.sh 2025-06-22 12:29:38 +02:00
b9d287b28c Remove encryptor.sh example 2025-06-22 12:29:38 +02:00
8688897a23 update README 2025-06-22 12:29:38 +02:00
4425b31804 Fix ancient correctness/UB issues
What was young me thinking? Probably nothing.

While mask_to_names() was not causing issues in practise, it was
rewritten to be more reasonable. Not that it is the only
thing that could use a rewrite...

Ah, to be young again...
2025-06-22 12:28:51 +02:00
修改 5 个文件,包含 96 行新增85 行删除

查看文件

@@ -1,11 +1,15 @@
prefix = /usr/local prefix = /usr/local
bindir = $(prefix)/bin bindir = $(prefix)/bin
CFLAGS = -std=c99 -Wall -Wextra -pedantic CFLAGS = -std=c99 -Wall -Wextra -pedantic
VERSIONFALLBACK = "v1.3+"
VERSIONFLAGS = -DGIT_TAG=\"$(shell git describe --tags HEAD || echo $(VERSIONFALLBACK))\"
all: all:
$(CC) adhocify.c -g $(CFLAGS) -o adhocify $(CC) adhocify.c -g $(CFLAGS) $(VERSIONFLAGS) -o adhocify
release: release:
$(CC) adhocify.c $(CFLAGS) -o adhocify $(CC) adhocify.c $(CFLAGS) $(VERSIONFLAGS) -o adhocify
install: release install: release
install -D adhocify $(DESTDIR)$(bindir)/adhocify install -D adhocify $(DESTDIR)$(bindir)/adhocify

查看文件

@@ -4,11 +4,7 @@ What is adhocify?
adhocify uses inotify to watch for file system events. Once an event adhocify uses inotify to watch for file system events. Once an event
occurs it can execute a command. The path of the file and the event occurs it can execute a command. The path of the file and the event
will be passed to that command. will be passed to that command.
Options
=======
See adhocify --help.
Examples: Examples:
========= =========
@@ -83,14 +79,17 @@ File: /tmp/test Event: IN_CLOSE,IN_CLOSE_WRITE
File: /tmp/test Event: IN_MODIFY File: /tmp/test Event: IN_MODIFY
``` ```
A second shell ran In this example, another shell ran
``` ```
chmod 600 /tmp/test chmod 600 /tmp/test
echo "test" >> /tmp/test echo "test" >> /tmp/test
``` ```
Passing ```-q``` would also keep adhocify silent, surpressing those "Starting execution..." messages. Passing ```-q``` keeps adhocify silent, surpressing those "Starting execution..." messages.
Options
=======
See adhocify --help.
Other tools Other tools
=========== ===========
@@ -101,17 +100,17 @@ Install
======= =======
## Debian / Ubuntu ## Debian / Ubuntu
Please [read this](https://quitesimple.org/page/repositories) before using those instructions.
Latest release can be installed using apt Latest release can be installed using apt
``` ```
# First, obtain key, assume it's trusted. # First, obtain the key. Here we just assume it's trustworthy...
wget -O- https://repo.quitesimple.org/repo.quitesimple.org.asc | gpg --dearmor > repo.quitesimple.org-keyring.gpg wget -O- https://repo.quitesimple.org/repo.quitesimple.org.asc | gpg --dearmor > repo.quitesimple.org-keyring.gpg
cat repo.quitesimple.org-keyring.gpg | sudo tee -a /usr/share/keyrings/repo.quitesimple.org.gpg > /dev/null cat repo.quitesimple.org-keyring.gpg | sudo tee -a /usr/share/keyrings/repo.quitesimple.org.gpg > /dev/null
#For Debian #For Ubuntu/Debian
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/repo.quitesimple.org.gpg] https://repo.quitesimple.org/debian/ default main" | sudo tee /etc/apt/sources.list.d/quitesimple.list echo "deb [arch=amd64 signed-by=/usr/share/keyrings/repo.quitesimple.org.gpg] https://repo.quitesimple.org/debian/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/quitesimple.list
#For Ubuntu >=21.10, prefer these sources
#echo "deb [arch=amd64 signed-by=/usr/share/keyrings/repo.quitesimple.org.gpg] https://repo.quitesimple.org/debian/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/quitesimple.list
sudo apt-get update sudo apt-get update
sudo apt-get install adhocify sudo apt-get install adhocify

查看文件

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2014-2024 Albert S. <adhocify@quitesimple.org> * Copyright (c) 2014-2025 Albert S. <adhocify@quitesimple.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
@@ -160,16 +160,11 @@ bool path_is_directory(const char *path)
return S_ISDIR(sb.st_mode); return S_ISDIR(sb.st_mode);
} }
static inline bool file_exists(const char *path)
{
return access(path, F_OK) == 0;
}
void add_to_ignore_list(const char *str) void add_to_ignore_list(const char *str)
{ {
*ignorelist_current = xmalloc(sizeof(struct ignorelist)); *ignorelist_current = xmalloc(sizeof(struct ignorelist));
(*ignorelist_current)->ignore = xstrdup(str); (*ignorelist_current)->ignore = xstrdup(str);
(*ignorelist_current)->next = NULL;
ignorelist_current = &(*ignorelist_current)->next; ignorelist_current = &(*ignorelist_current)->next;
} }
@@ -246,71 +241,77 @@ bool redirect_stdout(const char *outfile)
const char *mask_to_names(int mask) const char *mask_to_names(int mask)
{ {
static char ret[1024]; char ret[1024] = {0};
size_t n = sizeof(ret) - 1; FILE *f = fmemopen(ret, sizeof(ret), "w");
if(f == NULL)
{
logerror("fmemopen() failed\n");
return NULL;
}
if(mask & IN_ATTRIB) if(mask & IN_ATTRIB)
{ {
strncat(ret, "IN_ATTRIB,", n); fputs("IN_ATTRIB,", f);
} }
if(mask & IN_OPEN) if(mask & IN_OPEN)
{ {
strncat(ret, "IN_OPEN,", n); fputs("IN_OPEN,", f);
} }
if(mask & IN_CLOSE) if(mask & IN_CLOSE)
{ {
strncat(ret, "IN_CLOSE,", n); fputs("IN_CLOSE,", f);
} }
if(mask & IN_CLOSE_NOWRITE) if(mask & IN_CLOSE_NOWRITE)
{ {
strncat(ret, "IN_CLOSE,", n); fputs("IN_CLOSE_NOWRITE,", f);
} }
if(mask & IN_CLOSE_WRITE) if(mask & IN_CLOSE_WRITE)
{ {
strncat(ret, "IN_CLOSE_WRITE,", n); fputs("IN_CLOSE_WRITE,", f);
} }
if(mask & IN_CREATE) if(mask & IN_CREATE)
{ {
strncat(ret, "IN_CREATE,", n); fputs("IN_CREATE,", f);
} }
if(mask & IN_DELETE) if(mask & IN_DELETE)
{ {
strncat(ret, "IN_DELETE,", n); fputs("IN_DELETE,", f);
} }
if(mask & IN_DELETE_SELF) if(mask & IN_DELETE_SELF)
{ {
strncat(ret, "IN_DELETE_SELF,", n); fputs("IN_DELETE_SELF,", f);
} }
if(mask & IN_MODIFY) if(mask & IN_MODIFY)
{ {
strncat(ret, "IN_MODIFY,", n); fputs("IN_MODIFY,", f);
} }
if(mask & IN_MOVE) if(mask & IN_MOVE)
{ {
strncat(ret, "IN_MOVE,", n); fputs("IN_MOVE,", f);
} }
if(mask & IN_MOVE_SELF) if(mask & IN_MOVE_SELF)
{ {
strncat(ret, "IN_MOVE_SELF,", n); fputs("IN_MOVE_SELF,", f);
} }
if(mask & IN_MOVED_FROM) if(mask & IN_MOVED_FROM)
{ {
strncat(ret, "IN_MOVED_FROM,", n); fputs("IN_MOVED_FROM,", f);
} }
if(mask & IN_MOVED_TO) if(mask & IN_MOVED_TO)
{ {
strncat(ret, "IN_MOVED_TO,", n); fputs("IN_MOVED_TO,", f);
} }
long pos = ftell(f);
for(int i = n; i >= 0; --i) fclose(f);
if(pos > 0)
{ {
if(ret[i] == ',') ret[pos-1] = '\0';
{
ret[i] = 0;
break;
}
} }
ret[1023] = 0; else
return ret; {
logerror("Failed to convert mask to string: %s", strerror(errno));
exit(EXIT_FAILURE);
}
return xstrdup(ret);
} }
bool run_prog(const char *eventfile, uint32_t eventmask) bool run_prog(const char *eventfile, uint32_t eventmask)
@@ -329,8 +330,13 @@ bool run_prog(const char *eventfile, uint32_t eventmask)
if(!noenv) if(!noenv)
{ {
char envvar[30]; char envvar[30];
snprintf(envvar, sizeof(envvar), "ADHOCIFYEVENT=%" PRIu32, eventmask); snprintf(envvar, sizeof(envvar), "%" PRIu32, eventmask);
putenv(envvar); int ret = setenv("ADHOCIFYEVENT", envvar, 1);
if(ret != 0)
{
perror("setenv");
exit(EXIT_FAILURE);
}
} }
for(unsigned int i = 0; i < n_script_arguments; i++) for(unsigned int i = 0; i < n_script_arguments; i++)
@@ -344,7 +350,13 @@ bool run_prog(const char *eventfile, uint32_t eventmask)
} }
if(STREQ(argument, EVENTSTR_PLACEHOLDER)) if(STREQ(argument, EVENTSTR_PLACEHOLDER))
{ {
script_arguments[i] = mask_to_names(eventmask); const char *names = mask_to_names(eventmask);
if(names == NULL)
{
logerror("Failed to convert mask to strings");
exit(EXIT_FAILURE);
}
script_arguments[i] = names;
} }
} }
} }
@@ -434,6 +446,7 @@ void queue_watches_from_stdin()
line[r - 1] = 0; line[r - 1] = 0;
watchqueue_add_path(line); watchqueue_add_path(line);
} }
free(line);
} }
char *get_eventfile_abspath(struct inotify_event *event) char *get_eventfile_abspath(struct inotify_event *event)
@@ -497,7 +510,10 @@ static inline char *get_cwd()
void print_usage() void print_usage()
{ {
printf("adhocify [OPTIONS] command [arguments for command] - Monitor for inotify events and launch commands\n\n"); printf("adhocify [OPTIONS] command [arguments for command] - Monitor for inotify events and launch commands\n");
printf("Version: %s\n\n", GIT_TAG);
printf("--version, -V print version and exit\n");
printf("--daemon, -d run as a daemon\n"); printf("--daemon, -d run as a daemon\n");
printf("--path, -w adds the specified path to the watchlist\n"); printf("--path, -w adds the specified path to the watchlist\n");
printf("--logfile, -o path to write output of adhocify and stdout/stderr of launched commands to\n"); printf("--logfile, -o path to write output of adhocify and stdout/stderr of launched commands to\n");
@@ -507,11 +523,11 @@ void print_usage()
"environment variable\n"); "environment variable\n");
printf("--silent, -q surpress any output created by adhocify itself\n"); printf("--silent, -q surpress any output created by adhocify itself\n");
printf("--stdin, -s Read the paths which must be added to the watchlist from stdin. Each path must be " printf("--stdin, -s Read the paths which must be added to the watchlist from stdin. Each path must be "
"in a seperate line\n"); "in a seperate line.\n");
printf("--no-forkbomb-check, -b Disable fork bomb detection\n"); printf("--no-forkbomb-check, -b Disable fork bomb detection\n");
printf("--ignore, -i Shell wildcard pattern (see glob(7)) to ignore events on files for which the " printf("--ignore, -i Shell wildcard pattern (see glob(7)) to ignore events on files for which the "
"pattern matches\n"); "pattern matches\n");
printf("--exit-with-child, -e Exit when the commands exits. You can also specify a return code and negations (e. g. -e'!0' to " printf("--exit-with-child, -e Exit with the command. You can also specify a return code and negations (e. g. -e'!0' to "
"exit only on errors)\n"); "exit only on errors)\n");
printf("\nIf your command should know the file the event occured on, use the {} placeholder when you specify the " printf("\nIf your command should know the file the event occured on, use the {} placeholder when you specify the "
"arguments (like xargs)\n"); "arguments (like xargs)\n");
@@ -522,12 +538,14 @@ static struct option long_options[] = {{"daemon", no_argument, 0, 'd'},
{"mask", required_argument, 0, 'm'}, {"mask", required_argument, 0, 'm'},
{"path", required_argument, 0, 'w'}, {"path", required_argument, 0, 'w'},
{"no-env", no_argument, 0, 'a'}, {"no-env", no_argument, 0, 'a'},
{"version", no_argument, 0, 'V'},
{"stdin", no_argument, 0, 's'}, {"stdin", no_argument, 0, 's'},
{"no-forkbomb-check", no_argument, 0, 'b'}, {"no-forkbomb-check", no_argument, 0, 'b'},
{"ignore", required_argument, 0, 'i'}, {"ignore", required_argument, 0, 'i'},
{"silent", no_argument, 0, 'q'}, {"silent", no_argument, 0, 'q'},
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{"exit-with-child", optional_argument, 0, 'e'}}; {"exit-with-child", optional_argument, 0, 'e'},
{0,0,0,0}};
// fills global n_script_arguments and script_arguments var // fills global n_script_arguments and script_arguments var
void fill_script_arguments(size_t n_args, char *args[]) void fill_script_arguments(size_t n_args, char *args[])
@@ -538,13 +556,13 @@ void fill_script_arguments(size_t n_args, char *args[])
const char *argv0 = memrchr(prog, '/', strlen(prog)); const char *argv0 = memrchr(prog, '/', strlen(prog));
argv0 = (argv0 == NULL) ? prog : argv0 + 1; argv0 = (argv0 == NULL) ? prog : argv0 + 1;
arguments[0] = argv0; arguments[0] = xstrdup(argv0);
const int begin_offset = 1; const int begin_offset = 1;
for(unsigned int i = 0; i < n_args; i++) for(unsigned int i = 0; i < n_args; i++)
{ {
char *argument = args[i]; char *argument = args[i];
arguments[i + begin_offset] = strdup(argument); arguments[i + begin_offset] = xstrdup(argument);
} }
arguments[n_args + begin_offset] = NULL; arguments[n_args + begin_offset] = NULL;
@@ -557,10 +575,14 @@ void parse_options(int argc, char **argv)
int option; int option;
int option_index; int option_index;
uint32_t optmask = 0; uint32_t optmask = 0;
while((option = getopt_long(argc, argv, "absdo:w:m:l:i:e::", long_options, &option_index)) != -1) while((option = getopt_long(argc, argv, "absdVo:w:m:i:e::", long_options, &option_index)) != -1)
{ {
switch(option) switch(option)
{ {
case 'V':
printf("%s\n", GIT_TAG);
exit(EXIT_SUCCESS);
break;
case 'd': case 'd':
daemonize = true; daemonize = true;
break; break;
@@ -571,7 +593,7 @@ void parse_options(int argc, char **argv)
optmask = name_to_mask(optarg); optmask = name_to_mask(optarg);
if(optmask == 0) if(optmask == 0)
{ {
logerror("Not supported inotify event: %s\n", optarg); logerror("Unsupported inotify event: %s\n", optarg);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
mask |= optmask; mask |= optmask;
@@ -615,6 +637,9 @@ void parse_options(int argc, char **argv)
awaited_child_exit_code = atoi(optarg); awaited_child_exit_code = atoi(optarg);
} }
break; break;
case '?':
logerror("Invalid option provided. Exiting\n");
exit(EXIT_FAILURE);
} }
} }
@@ -642,6 +667,7 @@ void process_options()
if(watchlist_head == NULL) if(watchlist_head == NULL)
{ {
logwrite("Info: Watching current dir (no -w provided)\n");
watchqueue_add_path(get_cwd()); watchqueue_add_path(get_cwd());
} }
@@ -688,7 +714,7 @@ void wait_for_children()
} }
if(p == -1) if(p == -1)
{ {
logerror("waitpid failed when handling child exit\n"); logerror("waitpid() failed when handling child exit\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
int adhocify_exit_code = 0; int adhocify_exit_code = 0;
@@ -744,7 +770,6 @@ void start_monitoring(int ifd)
} }
while(offset < len) while(offset < len)
{ {
struct inotify_event *event = (struct inotify_event *)&buf[offset]; struct inotify_event *event = (struct inotify_event *)&buf[offset];
handle_event(event); handle_event(event);
offset += sizeof(struct inotify_event) + event->len; offset += sizeof(struct inotify_event) + event->len;
@@ -764,9 +789,10 @@ void child_handler(int signum, siginfo_t *info, void *context)
void set_signals() void set_signals()
{ {
struct sigaction action; struct sigaction action = {0};
action.sa_flags = SA_NOCLDSTOP | SA_SIGINFO; action.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
action.sa_sigaction = &child_handler; action.sa_sigaction = &child_handler;
sigemptyset(&action.sa_mask);
if(sigaction(SIGCHLD, &action, NULL) == -1) if(sigaction(SIGCHLD, &action, NULL) == -1)
{ {
logerror("Error when setting up the signal handler\n"); logerror("Error when setting up the signal handler\n");
@@ -788,10 +814,10 @@ int main(int argc, char **argv)
parse_options(argc, argv); parse_options(argc, argv);
process_options(); process_options();
int ifd = inotify_init(); int ifd = inotify_init1(O_CLOEXEC);
if(ifd == -1) if(ifd == -1)
{ {
perror("inotify_init"); perror("inotify_init1");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
create_watches(ifd, mask); create_watches(ifd, mask);

查看文件

@@ -1,19 +0,0 @@
#!/bin/sh
#example: encrypt files once they get written to a directory and remove them
#launch with: adhocify -w /path/encryptin /path/to/this/script.sh {}
#This is a simple example and has security flaws:
#-no secure delete (better to use e. g. ramfs or tmpfs...)
#-then still not necessarily secure against people who can dump the content of the memory
set -e
DESTINATION="/tmp/store"
if [ -z "$1" ] ; then
echo "Need path to encrypt" >&2
exit 1
fi
sleep 2 #some clients may want to set permissions and so on after writing
FILEPATH="$1"
gpg -e -r mail@example.com -o $DESTINATION/$(basename $FILEPATH) $FILEPATH
rm $FILEPATH

查看文件

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
#moves all incoming files (e. g. downloads) to another directory. #Hardlinks all incoming files (e. g. downloads) to another directory.
#There, they will be put into subdirectories which are named after the current date (YYYYMMDD) to get some minimal automatic "organization". #There, they will be put into subdirectories which are named after the current date (YYYYMMDD) to get some minimal automatic "organization".
#adhocify -d -m IN_CLOSE_WRITE -m IN_MOVED_TO -w /home/user/Downloads -w /home/user/other_dir /path/to/move_downloads.sh #adhocify -d -m IN_CLOSE_WRITE -m IN_MOVED_TO -w /home/user/Downloads -w /home/user/other_dir /path/to/move_downloads.sh
@@ -12,11 +12,12 @@ today=$(date +%Y%m%d)
TARGET_DIR="/target/dir/path" TARGET_DIR="/target/dir/path"
TODAY_DIR="$TARGET_DIR"/$today TODAY_DIR="$TARGET_DIR"/$today
if [ ! -d "$TODAY_DIR" ] ; then if [ ! -d "$TODAY_DIR" ] ; then
mkdir "$TODAY_DIR" mkdir "$TODAY_DIR"
rm -f "$TARGET_DIR"/today rm -f "$TARGET_DIR"/today
ln -s "$TODAY_DIR" "$TARGET_DIR"/today ln -s "$TODAY_DIR" "$TARGET_DIR"/today
fi fi
#You can also filter/grep the filename here and move certain patterns to other designated locations... # Nowadays, some browsers don't like it when files are moved away immediately and might report download failure. Use hardlinks so they don't complain.
mv "$INCOMING" "$TARGET_DIR"/$today/ # Alternatively, a sufficiently long enough "sleep" followed by "mv" might work
ln "$INCOMING" "$TARGET_DIR"/$today/