/* File:
 *   fork.c
 *
 * Author:
 *   Kurt Newman (code@lamer.org)
 *
 * Problem:
 *   I'm trying to see what actually happens to memory in a child process
 *   when it has been allocated before the call to fork() (the parent).
 *   Going to place sleep() calls in strategic places to prove that
 *   there are actually two memory spaces being used.  If the variables
 *   were in the same context, then our calls to free and not setting of
 *   buf to NULL would lead to an ugly scene.
 *
 * Answer:
 *   Well, if you notice in main(), 20 bytes were allocated for char *buf.
 *   As expected, everything was copied over to the newly forked child.  So,
 *   to technically prevent memory leaks AND to clean up our environment
 *   before exiting, you must free everything.  In this case, we calloc'd once,
 *   and free'd twice...because fork() copies everything for the child.
 *   This is also why we could modify our variable in one context, but
 *   not in another.
 *
 * Note:
 *   In everything I write, when I free() something, I ALWAYS set it to NULL
 *   just to make sure something else can't access the pointer and attempt
 *   to free() again (or perhaps even use it).  I'm not going to set buf to
 *   NULL to prove that there are indeed seperate memory spaces.
 *
 * Compile:
 *   gcc -Wall -pedantic -o fork fork.c
 */

#include <stdio.h>     /* printf/fprintf */
#include <string.h>    /* strerror() */
#include <stdlib.h>    /* calloc() and free() */
#include <sys/types.h> /* pid_t var type */
#include <unistd.h>    /* fork() & sleep() */
#include <signal.h>    /* signal() */
#include <sys/wait.h>  /* wait() */
#include <errno.h>     /* for use with syscalls */

/* the variables I'm going to observe */
char *buf=NULL;
int num;

/* simple function to print out the two above variables */
void printem(char *proc) {
   printf("(%s) (0x%X) buf: ", proc, (unsigned int)buf);
   if(!buf)
      printf("(null)\n");
   else
      printf("%s\n", buf);

   printf("(%s) num: %d\n", proc, num);
}

/* signal handler for SIGCHLD */
static void child(int signo) {
   pid_t pid;
   int wstat;

   pid = wait(&wstat);

   printf("Received SIGCHLD.  Child %d terminated with code %d\n", pid, wstat);
}

int main(void) {
   int i;
   pid_t pid;

   /* put crap in the vars */
   if(!(buf = calloc(20, sizeof(char)))) {
      fprintf(stderr, "Error allocating 20 bytes of memory!\n");
      return(1);
   }
   for(i=0; i<19; i++) buf[i] = 'a'; /* one less for terminating null */
   num = 69;

   /* register a signal handler for SIGCHLD.  no zombies! */
   signal(SIGCHLD, child);

   if((pid = fork()) < 0) {
      fprintf(stderr, "Fork error, %s\n", strerror(errno));
      free(buf); buf = NULL;
      return(1);
   }

   /* child process */
   if(pid == 0) {
      printem("unmodified-child");
      free(buf);
      num = 70;
      printf("(child) freed buf and modified num\n");
      printem("modified-child");

      exit(0);
   }

   /* parent process */

   /* give child a chance to play with its variables before we do */ 
   sleep(1);

   printem("unmodified-parent");

   /* in some cases, free'ing the same memory over again will cause
    * a segmentation fault (depends on OS).  Child process free'd earlier..
    * will this eat dirt? :) */
   free(buf);
   printf("(parent) freed buf and modified num\n");
   num = 71;
   printem("modified-parent");

   return(0);
}


