#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
+#include <sys/wait.h>
#include <unistd.h>
#ifdef USE_UTEMPTER
sigaction(SIGQUIT, &action, NULL);
sigaction(SIGUSR1, &action, NULL);
sigaction(SIGUSR2, &action, NULL);
-
- /* Collect zombies automatically without having to call waitpid(2). */
- signal(SIGCHLD, SIG_IGN);
}
static int open_tty(void) {
#endif
static int login(int ptm) {
#if defined(USE_UTEMPTER)
- return utempter_add_record(ptm, NULL);
+ int result = utempter_add_record(ptm, NULL);
+ /* Exit value of utempter_*() is not correct on all systems, e.g.
+ * FreeBSD() always returns 0. Checking the utmpx database manually is
+ * difficult because we don't know the exact values for ut_id and ut_line,
+ * therefore we only check the return value on systems known to return a
+ * useful value. */
+# ifndef __linux
+ result = 1;
+# endif
+ return result;
#elif defined(USE_UTMPX)
return set_utmpx(USER_PROCESS, ptm);
#else
}
static int logout(int ptm) {
#if defined(USE_UTEMPTER)
- return utempter_remove_record(ptm);
+ int result = utempter_remove_record(ptm);
+ /* See above. */
+# ifndef __linux
+ result = 1;
+# endif
+ return result;
#elif defined(USE_UTMPX)
return set_utmpx(DEAD_PROCESS, ptm);
#else
#endif
}
+static int wait_for_write(int fd, int timeout) {
+ fd_set rfds;
+ struct timeval tv;
+ int result;
+
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ result = select(fd + 1, &rfds, NULL, NULL, &tv);
+ if (result < 0) {
+ perror("select");
+ return 0;
+ }
+ if (result == 0) {
+ /* Timeout. */
+ return 0;
+ }
+
+ /* Got more data to read. */
+ return 1;
+}
+
static void pass_buffer_to_program(const char *buffer, size_t length, char **argv) {
int fds[2];
FILE *fh;
pid_t pid;
- /* Skip argv[0]. */
- argv++;
-
if (pipe(fds) != 0) {
perror("pipe");
return;
}
close(fds[0]);
- execvp(argv[0], argv);
- perror("execvp");
- exit(EXIT_FAILURE);
+ /* Double fork so `wall-notify` doesn't have to wait for the
+ * notification process to terminate. We can't just use
+ * signal(SIGCHLD, SIG_IGN); because utempter on at least FreeBSD
+ * doesn't work if SIGCHLD is ignored. */
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ exit(EXIT_FAILURE);
+ } else if (pid == 0) {
+ execvp(argv[0], argv);
+ perror("execvp");
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
}
/* father */
out:
close(fds[0]); /* read side */
fclose(fh);
+
+ if (waitpid(pid, NULL, 0) < 0) {
+ perror("waitpid");
+ }
}
static void handle_wall(int fd, char **argv) {
char buffer[4096];
ssize_t r;
+ assert(SSIZE_MAX <= SIZE_MAX);
while ((r = read(fd, buffer, sizeof(buffer))) > 0) {
- assert(SSIZE_MAX <= SIZE_MAX);
+ size_t space;
+ ssize_t r2;
+
+ /* To prevent partial messages (sometimes it takes multiple reads to
+ * get the complete message) wait for a short time to get the rest of
+ * the message. */
+ space = sizeof(buffer) - (size_t)r;
+ while (space > 0 && wait_for_write(fd, 1 /* second */)) {
+ r2 = read(fd, buffer + r, space);
+ if (r2 <= 0) {
+ break;
+ }
+ r += r2;
+ space -= (size_t)r2;
+ }
+
pass_buffer_to_program(buffer, (size_t)r, argv);
}
}
+static void usage(const char *argv0) {
+ fprintf(stderr, "usage: %s <cmd args..>\n", argv0);
+ exit(EXIT_FAILURE);
+}
+
int main(int argc, char **argv) {
int ptm, pts;
char *name;
+ const char *argv0;
+
+ argv0 = argv[0];
+ /* Don't pass our argv[0] to the notification program. */
+ argv++;
if (argc < 2) {
- fprintf(stderr, "usage: %s <cmd args..>\n", argv[0]);
- exit(EXIT_FAILURE);
+ usage(argv0);
}
ptm = open_tty();