#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 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;
}
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);
}
}