0

I am writing a program in which:

  • main process reads file line by line, and writes it to another file each time replacing the old line
  • child process reads that another file, and outputs lines on the console

How can I do that? So far I have:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

int main(int argc, char **argv)
{
    int pid, c;
    FILE *fpr, *fpw;
    char str[512];

    if((pid = fork()) == 0)
    {
        fpr = fopen("bufor", "r");
        if(fpr == NULL)
            exit(EXIT_FAILURE);

        while(fscanf(fpr, "%[^\n]\n", str) != EOF)
        {
            printf("%s", str);
        }

        fclose(fpr);
        exit(0);
    }

    fpr = fopen("/etc/profile", "r");
    if(fpr == NULL)
        exit(EXIT_FAILURE);

    if(fpr)
    {
        while(fscanf(fpr, "%[^\n]\n", str) != EOF)
        {
            fpw = fopen("bufor", "w+");
            if(sizeof(str) > 0)
            {
                fputs(str, fpw);
            }
            fclose(fpw);
        }
        fclose(fpr);
    }

    return 0;
}

It reads from a file, writes to another, etc, but there is no synchronization - the output is only the last line.

How can I do that?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
khernik
  • 2,059
  • 2
  • 26
  • 51
  • Make sure your child process exits properly. You want to `fclose(fpr);` and `exit(0);` after the `while (fscanf(…) != EOF)` loop. – Jonathan Leffler Apr 27 '15 at 07:16
  • Thanks! But anyways, how do I use signals to synchronize it here? – khernik Apr 27 '15 at 07:18
  • You need to set up a simple signal handler for SIGUSR1 with `sigaction()`; it can simply return. After each write, the parent needs to send SIGUSR1 to the child PID. After each read, the child needs to send SIGUSR1 to the parent PID. And after sending the signal, the process needs to go to sleep until woken by a signal. You can use `pause()` for that — although there are other functions such as `sigsuspend()` that could be used instead (but `sigpause()` is not one you should use). – Jonathan Leffler Apr 27 '15 at 07:21
  • If you wish to accomplish a simple IPC mechanism in which the parent process writes data and the child process prints it to screen, you're better off using pipes, there is a stack overflow example (reader and writer, in the answers) that demonstrates just that flow. (http://stackoverflow.com/questions/2784500/how-to-send-a-simple-string-between-two-programs-using-pipes) – Ishay Peled Apr 27 '15 at 08:18

1 Answers1

1

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
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278