This code seems to work. Make sure you understand it all before handing it off as your own. I called the file sigsync.c
and hence the program sigsync
, hence the chosen name for the environment variable.
Compilation:
gcc -g -O3 -std=gnu11 -Wall -Wextra -Wmissing-prototypes \
-Wstrict-prototypes -Werror sigsync.c -o sigsync
Code:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int verbose = 0;
static volatile sig_atomic_t sig_num = 0;
static void sigusr1(int signum)
{
sig_num = signum;
}
static void be_childish(const char *file, pid_t parent)
{
char str[512];
FILE *fpr = fopen(file, "r");
if (fpr == NULL)
{
fprintf(stderr, "Failed to open file %s for reading\n", file);
exit(EXIT_FAILURE);
}
while (1)
{
rewind(fpr);
pause();
if (verbose)
printf("Child: got %d\n", sig_num);
while (fscanf(fpr, "%511[^\n]\n", str) == 1)
printf("%s\n", str);
kill(parent, SIGUSR1);
sig_num = 0;
}
/*NOTREACHED*/
fclose(fpr);
}
static void be_parental(const char *file, pid_t child)
{
char str[512];
const char profile[] = "/etc/profile";
FILE *fpr = fopen(profile, "r");
if (fpr == NULL)
{
fprintf(stderr, "Failed to open file %s for reading\n", profile);
exit(EXIT_FAILURE);
}
while (fscanf(fpr, "%511[^\n]\n", str) != EOF)
{
if (strlen(str) > 0)
{
FILE *fpw = fopen(file, "w");
if (fpw == 0)
{
fprintf(stderr, "Failed to open file %s for reading\n", profile);
kill(child, SIGTERM);
exit(EXIT_FAILURE);
}
fprintf(fpw, "%s\n", str);
fclose(fpw);
kill(child, SIGUSR1);
pause();
if (verbose)
printf("Parent: got %d\n", sig_num);
sig_num = 0;
}
}
fclose(fpr);
kill(child, SIGTERM);
}
int main(void)
{
int child;
int parent = getpid();
const char filename[] = "bufor";
/* Make sure file exists and is empty */
FILE *fp = fopen(filename, "w");
if (fp == 0)
{
fprintf(stderr, "Failed to open file %s for writing\n", filename);
exit(EXIT_FAILURE);
}
fclose(fp);
if (getenv("SIGSYNC_VERBOSE") != 0)
verbose = 1;
struct sigaction sa;
sa.sa_handler = sigusr1;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGUSR1, &sa, 0) != 0)
{
fprintf(stderr, "Failed to set signal handler\n");
exit(EXIT_FAILURE);
}
if ((child = fork()) < 0)
{
fprintf(stderr, "Failed to fork\n");
exit(EXIT_FAILURE);
}
else if (child == 0)
be_childish(filename, parent);
else
be_parental(filename, child);
return 0;
}
Sample output:
Note that the leading spaces are completely removed. The code in the parent reading /etc/profile
does that, and the reason why is subtle. The "%511[^\n]\n"
format does not skip leading white space, but the \n
at the end is not treated as 'only match a newline' but as 'match a sequence of white space'. This means it skips over the newline and leading white space on the next line. To preserve the space, use fgets()
or getline()
instead of fscanf()
. Tested on an Ubuntu 14.04 derivative with GCC 5.1.0.
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
umask 027
if [ "$PS1" ]; then
if [ "$BASH" ]; then
PS1='\u@\h:\w\$ '
if [ -f /etc/bash.bashrc ]; then
. /etc/bash.bashrc
fi
else
if [ "`id -u`" -eq 0 ]; then
PS1='# '
else
PS1='$ '
fi
fi
fi
if [ -d /etc/profile.d ]; then
for i in /etc/profile.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
unset i
fi