Sunday, September 28, 2014

Unix Prog: Controlling Terminal

1. Controlling Terminal
1) A session can have one controlling terminal. It may be terminal device(terminal login) or pseudo-terminal device(network login)
2) The session leader that establishes the connection to the controlling terminal is called the controlling process.
3) The process groups in the session can be divided into one foreground process group and one or more background process groups
4) Whenever we type one interrupt key(DELETE or Ctrl - C) or quit key, this causes the signal be sent to all processes in the foreground process groups
5) If a modem(or network) disconnect is detected by the terminal interface, the hang-up signal is sent to the controlling process(the session leader)

The way a program guarantees that it is talking to the controlling terminal is to open the file /dev/tty. If the program doesn't have one controlling terminal, open will fail.

2. tcgetpgrp, tcsetpgrp, tcgetsid

System Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h  
 ......  
 /* Return the foreground process group ID of FD. */  
 extern __pid_t tcgetpgrp (int __fd) __THROW;  
   
 /* Set the foreground process group ID of FD set PGRP_ID. */  
 extern int tcsetpgrp (int __fd, __pid_t __pgrp_id) __THROW;  
 ......  
 ubuntu@ip-172-31-23-227:~$ less /usr/include/termios.h  
 ......  
 /* Get process group ID for session leader for controlling terminal FD. */  
 extern __pid_t tcgetsid (int __fd) __THROW;  
 ......  

session.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<termios.h>  
 #include<fcntl.h>  
   
 int main(int argc, char* argv[])  
 {  
  int fd;  
   
  if((fd = open("/dev/tty", O_RDONLY)) < 0) {  
   printf("open error!\n");  
   exit(1);  
  }  
   
  printf("controlling terminal fd: %d\n", fd);  
  printf("current process id: %d\n", getpid());  
  printf("current process group id: %d\n", getpgrp());  
  printf("current process session id: %d\n", getsid(0));  
  printf("foreground group id with controlling terminal: %d\n", tcgetpgrp(fd));  
  printf("foreground group id with terminal 0: %d\n", tcgetpgrp(0));  
  printf("session leader group id with controlling terminal: %d\n", tcgetsid(fd));  
   
  // Change the foreground process group to the session leader group  
  if(tcsetpgrp(fd, getsid(0)) < 0) {  
   printf("tcsetgrp error!\n");  
   exit(2);  
  }  
   
  printf("After changing the foreground process group to the session leader group:\n");  
  printf("foreground group id with controlling terminal: %d\n", tcgetpgrp(fd));  
   
  exit(0);  
 }  

shell:
1) run the ps to display current running process, our bash shell is running, process id is 12478
2) Run the session.out,
By opening the "/dev/tty" its controlling terminal file descriptor is 3
Its current process id is 12585, it is the process group leader of group 12585
getsid(0) indicates that current process belong to the session with session leader 12478, bash shell process
tcgetpgrp indicates that current foreground process group is 12585, current process
And then we can tcsetpgrp to change the foreground process group to session leader group, bash shell
At this time, tcgetpgrp indicates that the foreground process group is already changed to bash shell: 12478.
 ubuntu@ip-172-31-23-227:~$ ps  
  PID TTY     TIME CMD  
 12478 pts/2  00:00:00 bash  
 12584 pts/2  00:00:00 ps  
 ubuntu@ip-172-31-23-227:~$ ./session.out  
 controlling terminal fd: 3  
 current process id: 12585  
 current process group id: 12585  
 current process session id: 12478  
 foreground group id with controlling terminal: 12585  
 foreground group id with terminal 0: 12585  
 session leader group id with controlling terminal: 12478  
 After changing the foreground process group to the session leader group:  
 foreground group id with controlling terminal: 12478  

Unix Prog: Sessions

1. Introduction:

The session is the collection of process groups


2. setsid, getsid

System Definition:
session id: the session leader process id, which is also the session leader's process group id. In other words, getsid return the process group id of calling process's session leader. In some systems, if the argument pid doesn't belong to the same session of the caller process, it will return error.
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h  
 ......  
 /* Create a new session with the calling process as its leader.  
   The process group IDs of the session and the calling process  
   are set to the process ID of the calling process, which is returned. */  
 extern __pid_t setsid (void) __THROW;  
   
 #if defined __USE_XOPEN_EXTENDED || defined __USE_XOPEN2K8  
 /* Return the session ID of the given process. */  
 extern __pid_t getsid (__pid_t __pid) __THROW;  
 ......  

What happens after calling setsid():
1) The process becomes the session leader of this new session. And the process is the only process in the new session
2) The process becomes the process group leader of a new process group.
3) The process has no controlling terminal. If it had one, the association will be broken.

The function returns an error if the caller is already a process group leader, to ensure it is not, the usual practice is to call fork and have the parent terminate and let child continue. Since child process belong to parent process's group, it child process is not the leader.

Example:
session.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 int main(int argc, char* argv[])  
 {  
  pid_t sid;  
   
  // Retrieve the session id of given process, if argument is 0  
  // getsid will use current process id  
  if((sid = getsid(0)) < 0) {  
   printf("getsid error!\n");  
   exit(1);  
  }  
   
  printf("current process session id: %d\n", sid);  
  printf("current process id: %d\n", getpid());  
  printf("current process group id: %d\n", getpgrp());  
   
  // It would be an error here, since current process is the  
  // process group lead  
  if((sid = setsid()) < 0) {  
   printf("setsid error!\n");  
   exit(2);  
  }  
   
  exit(0);  
 }  

shell:
Since process is the leader of the process group, it can't start a new session.
 ubuntu@ip-172-31-23-227:~$ ./session.out  
 current process session id: 12050  
 current process id: 12306  
 current process group id: 12306  
 setsid error!  

==================================================================
session.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 int main(int argc, char* argv[])  
 {  
  pid_t sid;  
  pid_t pid;  
   
  // Retrieve the session id of given process, if argument is 0  
  // getsid will use current process id  
  if((sid = getsid(0)) < 0) {  
   printf("getsid error!\n");  
   exit(1);  
  }  
   
  printf("parent process session id: %d\n", sid);  
  printf("parent process id: %d\n", getpid());  
  printf("parent process group id: %d\n", getpgrp());  
   
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(2);  
  }  
  else if (pid != 0) { // parent process  
   exit(0);  
  }  
   
  printf("before setting up the session: \n");  
  printf("child process id: %d\n", getpid());  
  printf("child process group id: %d\n", getpgrp());  
  printf("child process session id: %d\n", sid);  
   
  if((sid = setsid()) < 0) {  
   printf("setsid error!\n");  
   exit(3);  
  }  
   
  printf("after setting up the session: \n");  
  printf("child process id: %d\n", getpid());  
  printf("child process group id: %d\n", getpgrp());  
  printf("child process session id: %d\n", sid);  
  exit(0);  
 }  

shell:
The parent process session id: 12050, process id: 12339, process group id: 12339. After fork a child process, the child process id: 12340, process group id: 12339, session id: 12050. Child process inherits the process group id and session id from the parent process.
After setting up the new session inside the child process, the group id becomes: 12340, session id becomes 12340.
 ubuntu@ip-172-31-23-227:~$ ./session.out  
 parent process session id: 12050  
 parent process id: 12339  
 parent process group id: 12339  
 ubuntu@ip-172-31-23-227:~$ before setting up the session:  
 child process id: 12340  
 child process group id: 12339  
 child process session id: 12050  
 after setting up the session:  
 child process id: 12340  
 child process group id: 12340  
 child process session id: 12340  

Unix Prog: Process Groups

1. getpgrp, getpgid

system definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h  
 ......  
 /* Get the process group ID of the calling process. */  
 extern __pid_t getpgrp (void) __THROW;  
   
 /* Get the process group ID of process PID. */  
 extern __pid_t __getpgid (__pid_t __pid) __THROW;  
 ......  

for getpgid, if pid is 0, the calling process's id is used.
So getpgrp() = getpgid(0)

Example:
pg.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 int main(int argc, char* argv[])  
 {  
  printf("getpid: %d\n", getpid());  
  printf("getpgrp: %d\n", getpgrp());  
  printf("getpgid: %d\n", getpgid(0));  
  exit(0);  
 }  

shell:
the program outputs the process id, process group id.
In this case, process id is equal to process group id, which means this process is the leader of process group.
 ubuntu@ip-172-31-23-227:~$ ./pg.out  
 getpid: 12136  
 getpgrp: 12136  
 getpgid: 12136  

Process Group Life Cycle:
The process group will exist as long as there is more than 0 process in this group, even if the process is not the leader. The last remaining process in the group can choose to terminate the process group or join another process group.

2. setpgid

System Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h  
 ......  
 /* Set the process group ID of the process matching PID to PGID.  
   If PID is zero, the current process's process group ID is set.  
   If PGID is zero, the process ID of the process is used. */  
 extern int setpgid (__pid_t __pid, __pid_t __pgid) __THROW;  
 ......  

A process can only change the process group of itself and any of its children process.

Example:
pg.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 int main(int argc, char* argv[])  
 {  
  pid_t pid;  
   
  printf("Parent Process Id: %d\n", getpid());  
  printf("Parent Process Group Id: %d\n", getpgrp());  
   
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if(pid == 0) { // child process  
   printf("Child Process Id: %d\n", getpid());  
   printf("Child Process Group Id: %d\n", getpgrp());  
   sleep(3);  
   printf("After sleeping, child process group id: %d\n", getpgrp());  
   exit(0);  
  }  
   
  sleep(2);  
  printf("setup child process group to %d\n", pid);  
  if(setpgid(pid, pid) == -1) {  
   printf("setpgid error!\n");  
   exit(2);  
  }  
   
  exit(0);  
 }  

shell:
When launching the parent process, its id is 12216, and its group id is 12216 too. After "fork" another child process, the parent process start sleeping for 2 seconds. During this period, child process output is process id: 12217, process group id 12216, so child process is belonging to the parent process's group by default. After parent process wake up, it change child process's group to 12217, and after child process wake up, the operation prove to be successful.
 ubuntu@ip-172-31-23-227:~$ ./pg.out  
 Parent Process Id: 12216  
 Parent Process Group Id: 12216  
 Child Process Id: 12217  
 Child Process Group Id: 12216  
 setup child process group to 12217  
 ubuntu@ip-172-31-23-227:~$ After sleeping, child process group id: 12217  

Note: the child process is always assumed to be part of parent process group by default. This happened before any re-assignment of child process group in parent process or child process, to avoid race condition.

Unix Prog: Terminal Logins

1. Oldest way to login
In the oldest system, dumb terminals that were connected to the host with hard-wired connections. These logins came through a terminal device driver in the kernel. A host has a fixed number of terminal devices, so there is known upper limit on the number of simultaneous logins.

And window system are developed to provide more ways to interact with host.

2. BSD Terminal Login


1) The system administrator creates a file, usually /etc/ttys, that has one line per terminal device. Each line specifies the name of the device and other parameters that are passed to the getty program.

2) Init process read each line of /etc/ttys, for every terminal device that allows the login, does a fork followed by an exec of the program getty.

3) "getty" then calls "open" for the terminal device, which assigns the file descriptor 0, 1, 2, and then display "login: ", waiting for user to enter the user name

4) After user input the the user name, getty will execute the "login" program with user name and self-made environment variables.

execle("/bin/login", "login", "-p", username, (char*)0, envp);

5) "login" program will fetch the user record with "getpwnam" with our user name from passwd file, then it will call getpass to display prompt "Password:", waiting for user input password. After user input the password, it will call crypt to encrypt the input and compare with encrypted password from the shadow password entry.

6) If login correctly, "login" will
change to our home directory
change the ownership of terminal device, so user could own it
change the access permissions for terminal device so user could have permission to read from and write to it.
set group ids by calling setgid and initgroups
initialize the environment variable
change to user id(setuid)
invoke login shell "/bin/sh" or other specified shell
and many more......

7) after login shell is invoked, it will read some start-up files like "./profile" for the bourne shell/korn shell;".bash_profile, .bash_login" for GNU shell, ".cshrc, .login" for the c shell, in order to change some of the environment variables and add many additional variables to the environment.

Note: getty, login, login shell. All of their parent process is "init process".

3. Mac OS X Terminal Login
Same as BSD Terminal Login

4. Linux Terminal Login
Same as BSD Terminal Login, the only difference is the configuration file describing the terminal device is /etc/inittab instead of /etc/ttys

5. Solaris Terminal Login
Solaris provide one more different way compared to traditional getty way.
Its init process will "fork" SAC(Service access Controller), SAC will then fork and exec "ttymon"(monitor all terminal ports). If needed, ttymon will fork and exec the "login" to handle login process. So at Solaris, "login, login shell"'s parent process is ttymon instead of "init".

6. Network Login -- BSD
The main difference between network login and login through a serial terminal is: in network login, "login" is a service, waiting for network connection request coming.



1) inetd process waits for most network connections, inetd is called "Internet superserver"
2) once the request arrive, inetd does a "fork" and "exec" of the appropriate program
3) Take telnet as example, if user type: "telnet hostname"
The client opens a TCP connection to hostname, and the program that's started on hostname is called TELNET server, the client and the server then exchange data across the TCP connection using the TELNET application protocol.
4) telnetd then opens a pseudo-terminal device and splits into two processes using fork. The parent(telnetd) handles communication across the network connection, and the child does an exec of the login program.

Unix Prog: Process Times

1. System Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/x86_64-linux-gnu/sys/times.h  
 ......  
 /* Structure describing CPU time used by a process and its children. */  
 struct tms  
  {  
   clock_t tms_utime;     /* User CPU time. */  
   clock_t tms_stime;     /* System CPU time. */  
   
   clock_t tms_cutime;     /* User CPU time of dead children. */  
   clock_t tms_cstime;     /* System CPU time of dead children. */  
  };  
   
   
 /* Store the CPU time used by this process and all its  
   dead children (and their dead children) in BUFFER.  
   Return the elapsed real time, or (clock_t) -1 for errors.  
   All times are in CLK_TCKths of a second. */  
 extern clock_t times (struct tms *__buffer) __THROW;  
 ......  

2. Example

times.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<sys/times.h>  
   
 void pr_times(clock_t real, struct tms *tmstart, struct tms *tmend)  
 {  
  static long clktck = 0;  
   
  // Get the constant: number of clock tick per second  
  if(clktck == 0) {  
   if((clktck = sysconf(_SC_CLK_TCK)) < 0) {  
    printf("sysconf Error!\n");  
    exit(3);  
   }  
  }  
   
  printf("  real:  %7.2f\n", real / (double)clktck);  
  printf("  user:  %7.2f\n", (tmend->tms_utime - tmstart->tms_utime) / (double)clktck);  
  printf("  sys:  %7.2f\n", (tmend->tms_stime - tmstart->tms_stime) / (double)clktck);  
  printf("child user: %7.2f\n", (tmend->tms_cutime - tmstart->tms_cutime) / (double)clktck);  
  printf("child sys: %7.2f\n", (tmend->tms_cstime - tmstart->tms_cstime) / (double)clktck);  
 }  
   
 void do_cmd(char *cmd)  
 {  
  struct tms tmstart, tmend;  
  clock_t start ,end;  
  int status;  
   
  // Retrieve the current time information  
  printf("\ncommand: %s\n", cmd);  
  if((start = times(&tmstart)) == -1) {  
   printf("times error!\n");  
   exit(1);  
  }  
   
  // Run the command  
  if((status = system(cmd)) < 0) {  
   printf("system error!\n");  
   exit(2);  
  }  
   
  // Retrieve the time information after running the command  
  if((end = times(&tmend)) == -1) {  
   printf("times error!\n");  
   exit(1);  
  }  
   
  pr_times(end-start, &tmstart, &tmend);  
 }  
   
 int main(int argc, char* argv[])  
 {  
  int i;  
   
  setbuf(stdout, NULL);  
  for(i=1; i < argc; i++) {  
   do_cmd(argv[i]);  
  }  
  exit(0);  
 }  

shell:
 ubuntu@ip-172-31-23-227:~$ ./times.out "sleep 5"  
   
 command: sleep 5  
   real:   5.00  
   user:   0.00  
   sys:    0.00  
 child user:  0.00  
 child sys:   0.00  

Saturday, September 27, 2014

Unix Prog: User Identification

1. getlogin

It is possible that single user id in passwd file can map to different entries with different login name. getlogin system call can return the current login name.

Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h  
 ......  
 /* Return the login name of the user.  
   
   This function is a possible cancellation point and therefore not  
   marked with __THROW. */  
 extern char *getlogin (void);  
 ......  

2. Example:

record.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<pwd.h>  
   
 int main(int argc, char* argv[])  
 {  
  char *login;  
   
  // Retrieve and output the login name  
  if((login = getlogin()) == NULL) {  
   printf("getlogin error!\n");  
   exit(1);  
  }  
   
  printf("%s\n", login);  
   
  // Retrieve the passwd record based on login name  
  struct passwd *pw;  
  if((pw = getpwnam(login)) == NULL) {  
   printf("getpwnam error!\n");  
   exit(2);  
  }  
   
  printf("Username: %s, Password: %s, User ID: %d, Group ID: %d, \n",  
      pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid);  
  printf("realname: %s, home dir: %s, shell: %s\n", pw->pw_gecos, pw->pw_dir, pw->pw_shell);  
   
  exit(0);  
 }  

shell:
After getting the current login name with "getlogin()", then we use getpwnam to retrieve the password record from passwd file based on retrieved login name.
 ubuntu@ip-172-31-23-227:~$ ./record.out  
 ubuntu  
 Username: ubuntu, Password: x, User ID: 1000, Group ID: 1000,  
 realname: Ubuntu, home dir: /home/ubuntu, shell: /bin/bash  

Unix Prog: Process Accounting

1. Enable the process accounting
 ubuntu@ip-172-31-23-227:~$ accton --help  
 Usage: accton [OPTION] on|off|ACCOUNTING_FILE  
   
      Turns process accounting on or off, or changes the file where this  
      info is saved.  
   
      OPTIONS:  
      -h, --help    Show help and exit  
      -V, --version  Show version and exit  
   
      ARGUMENTS:  
      on        Activate process accounting and use default file  
      off       Deactivate process accounting  
      ACCOUNTING_FILE Activate (if not active) and save information in  
      this file  
   
      The system's default process accounting file is '/var/log/account/pacct'.  
   
      Report bugs to <bug-acct@gnu.org>  

shell:
 ubuntu@ip-172-31-23-227:~$ sudo accton ./own_acct  
 Turning on process accounting, file set to './own_acct'.  
 ubuntu@ip-172-31-23-227:~$ ls -lrt own_acct  
 -rw-rw-r-- 1 ubuntu ubuntu 128 Sep 27 19:27 own_acct  

2. Process Accounting Data Structure

 ubuntu@ip-172-31-23-227:~$ less /usr/include/x86_64-linux-gnu/sys/acct.h  
 ......  
 /*  
  comp_t is a 16-bit "floating" point number with a 3-bit base 8  
  exponent and a 13-bit fraction. See linux/kernel/acct.c for the  
  specific encoding system used.  
 */  
   
 typedef u_int16_t comp_t;  
   
 struct acct  
 {  
  char ac_flag;         /* Flags. */  
  u_int16_t ac_uid;       /* Real user ID. */  
  u_int16_t ac_gid;       /* Real group ID. */  
  u_int16_t ac_tty;       /* Controlling terminal. */  
  u_int32_t ac_btime;      /* Beginning time. */  
  comp_t ac_utime;       /* User time. */  
  comp_t ac_stime;       /* System time. */  
  comp_t ac_etime;       /* Elapsed time. */  
  comp_t ac_mem;        /* Average memory usage. */  
  comp_t ac_io;         /* Chars transferred. */  
  comp_t ac_rw;         /* Blocks read or written. */  
  comp_t ac_minflt;       /* Minor pagefaults. */  
  comp_t ac_majflt;       /* Major pagefaults. */  
  comp_t ac_swaps;       /* Number of swaps. */  
  u_int32_t ac_exitcode;    /* Process exitcode. */  
  char ac_comm[ACCT_COMM+1];  /* Command name. */  
  char ac_pad[10];       /* Padding bytes. */  
 };  
 ......  

Macro used to parse "ac_flag" field:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/x86_64-linux-gnu/sys/acct.h  
 ......  
 enum  
  {  
   AFORK = 0x01,        /* Has executed fork, but no exec. */  
   ASU = 0x02,         /* Used super-user privileges. */  
   ACORE = 0x08,        /* Dumped core. */  
   AXSIG = 0x10        /* Killed by a signal. */  
  };  
 ......  

Notes:
1) ac_btime: beginning time records when the process starts, but the time only end up with accuracy of second level, ac_etime: elapsed time records how many clock ticks the process uses. Based on ac_btime, ac_etime and ending order of process accounting record, we can't get the starting order of each process, if each process starts at the same second.

2) If Program A runs exec to launch program B which then run exec to launch program C, "A B C" happens at the same process, so only one process accounting record is written to describe 3 programs.

3. Example:

Process.c will create several processes, each process will be terminated in different ways.

And then we can use accounting.c to read the process accounting file

process.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<signal.h>  
   
 int main(int argc, char* argv[])  
 {  
  pid_t pid;  
   
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if(pid != 0) { // parent process  
   sleep(2);  
   exit(2);  
  }  
   
  // First Child Process  
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if(pid != 0) {  
   sleep(4);  
   abort();  
  }  
   
  // Second Child Process  
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if(pid != 0) {  
   execl("/usr/bin/date", "date", (char*)0);  
   exit(7);  
  }  
   
  // Third Child Process  
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if(pid != 0) {  
   sleep(8);  
   exit(0);  
  }  
   
  // Fourth Child Process  
  sleep(6);  
  kill(getpid(), SIGKILL); // the process will be terminated immediately  
 }  

accounting.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<sys/acct.h>  
 #include<unistd.h>  
   
 #ifdef HAS_SA_STAT  
 #define FMT "%s e = %6ld, chars = %7ld, stat = %3u: %c %c %c %c\n"  
 #else  
 #define FMT "%s e = %6ld, chars = %7ld, %c %c %c %c\n"  
 #endif  
 #ifndef HAS_ACORE  
 #define ACORE 0  
 #endif  
 #ifndef HAS_AXSIG  
 #define AXSIG 0  
 #endif  
   
 static unsigned long compt2ulong(comp_t comptime)  
 {  
  unsigned long val;  
  int exp;  
   
  val = comptime & 0x1fff; // val stores lower 13 bits of comptime  
  exp = (comptime >> 13) & 7; // exp stores lower position 16 15 14, 3 bits of value  
   
  while(exp-- > 0)  
   val *= 8;  
  return (val);  
 }  
   
 int main(int argc, char* argv[])  
 {  
  struct acct acdata;  
  FILE *fp;  
   
  if(argc != 2) {  
   printf("usage: pracct filename\n");  
   exit(1);  
  }  
   
  if((fp = fopen(argv[1], "r")) == NULL) {  
   printf("fopen error!\n");  
   exit(2);  
  }  
   
  // Read the accounting record, and output to shell  
  while(fread(&acdata, sizeof(acdata), 1, fp) == 1) {  
   printf(FMT, acdata.ac_comm, compt2ulong(acdata.ac_etime), compt2ulong(acdata.ac_io),  
 #ifdef HAS_SA_STAT  
       (unsigned char) acdata.ac_stat,  
 #endif  
       acdata.ac_flag & ACORE ? 'D' : ' ',  
       acdata.ac_flag & AXSIG ? 'X' : ' ',  
       acdata.ac_flag & AFORK ? 'F' : ' ',  
       acdata.ac_flag & ASU  ? 'S' : ' ');  
  }  
   
  if(ferror(fp)) {  
   printf("read error!\n");  
   exit(3);  
  }  
   
  exit(0);  
 }  

shell:
 ubuntu@ip-172-31-23-227:~$ sudo accton ./own_acct  
 Turning on process accounting, file set to './own_acct'.  
 ubuntu@ip-172-31-23-227:~$ ./process.out  
 ubuntu@ip-172-31-23-227:~$ ./pracct.out ./own_acct  

Unix Prog: System Function

1. Definition:

 /* Execute the given line as a shell command.  
   
   This function is a cancellation point and therefore not marked with  
   __THROW. */  
 extern int system (const char *__command) __wur;  

system function is implemented with "fork", "exec" and "waitpid"
If argument is NULL, it will return non-zero if system function is supported.
If "fork" fails, it will return -1.
If "exec" fails, it will return 127.
If "everything succeed", it will return status specified by "waitpid"

2. Own System Implementation without signal handling

system.c:
 #include<sys/wait.h>  
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 void pr_exit(int status)  
 {  
  if(WIFEXITED(status)) {  
   printf("normal termination: exit status = %d\n", WEXITSTATUS(status));  
  }  
  else if(WIFSIGNALED(status)) {  
   printf("abnormal termination: exit status = %d%s\n",  
       WTERMSIG(status), WCOREDUMP(status)? "(core file generated)" : "");  
  }  
  else if(WIFSTOPPED(status)) {  
   printf("child stopped, signal number = %d\n", WSTOPSIG(status));  
  }  
 }  
   
 int system(const char* cmdstring)  
 {  
  int status;  
  pid_t pid;  
   
  // Return non-zero if argument is NULL and system function is supported  
  // in current system  
  if(cmdstring == NULL)  
   return 1;  
   
  // If "fork" fail, return -1  
  if((pid = fork()) < 0) {  
   status = -1;  
  } else if (pid == 0) {  
   execl("/bin/sh", "sh", "-c", cmdstring, (char*)0);  
   _exit(127); // if "exec" fails, return status 127  
  } else {  
   if(waitpid(pid, &status, 0) < 0) {  
    printf("waitpid error!\n");  
    status = -1; // if "waitpid" fails, return status -1  
   }  
  }  
   
  return(status);  
 }  
   
 int main(int argc, char* argv[])  
 {  
  int status;  
   
  status = system("date");  
  pr_exit(status);  
   
  status = system("nosuchcommand");  
  printf("status: %d\n", status); // This is wrong, only part of bits indicate the return code  
                  // We need to use macro to get it.  
  pr_exit(status);  
   
  exit(0);  
 }  

shell:
1) First line, output by command "date"
2) Second line, output by pr_exit per status returned by "date"
3) Third line, output by command "nosuchcommand"
4) Fourth line, output by printing out status directly, which is wrong value, since only parts of bits are returning status
5) Fifth line, use macro to retrieve the termination status, output 127 successfully.
 ubuntu@ip-172-31-23-227:~$ ./system.out  
 Sat Sep 27 16:05:57 UTC 2014  
 normal termination: exit status = 0  
 sh: 1: nosuchcommand: not found  
 status: 32512  
 normal termination: exit status = 127  

The advantage of using "system" function is it has all error handling and signal handling, in our above implementation, we don't have any signal handling yet.

3. Security hole

tsys.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 int main(int argc, char* argv[])  
 {  
  int status;  
   
  if(argc < 2){  
   printf("command-line arguments required!\n");  
   exit(1);  
  }  
   
  if((status = system(argv[1])) < 0) {  
   printf("system error!\n");  
   exit(2);  
  }  
   
  exit(0);  
 }  

printuid.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 int main(int argc, char* argv[])  
 {  
  printf("real user id: %d, effective user id: %d\n", getuid(), geteuid());  
  exit(0);  
 }  

shell:
1) Run tsys.out to execute printuid.out, and real user id and effective user id are both 1000
2) change the owner of tsys.out to "root", super user
3) setup the set-user-bit at tsys.out, so any process running tsys.out, its effective user id will be set to the owner of tsys.out, root
4) List out task files.
5) Run the tsys.out to execute printuid.out, current process's effective user id is already changed to "root". So it means, any user can use tsys.out to do whatever we want, which is a serious security hole.
 ubuntu@ip-172-31-23-227:~$ ./tsys.out ./printuid.out  
 real user id: 1000, effective user id: 1000  
 ubuntu@ip-172-31-23-227:~$ sudo chown root ./tsys.out  
 ubuntu@ip-172-31-23-227:~$ sudo chmod u+s ./tsys.out  
 ubuntu@ip-172-31-23-227:~$ ls -lrt ./*.out  
 -rwsrwxr-x 1 root  ubuntu 9748 Sep 27 17:33 ./tsys.out  
 -rwxrwxr-x 1 ubuntu ubuntu 9775 Sep 27 17:36 ./printuid.out  
 ubuntu@ip-172-31-23-227:~$ ./tsys.out ./printuid.out  
 real user id: 1000, effective user id: 0  

Wednesday, September 24, 2014

Unix Prog: Interpreter Files

1. Introduction

When system run the interpreter file, it will run the first line's program + interpreter file, instead of running interpreter file itself.

 ubuntu@ip-172-31-23-227:~$ cat ./ifile  
 #! /bin/echo  
 ubuntu@ip-172-31-23-227:~$ ./ifile  
 ./ifile  

In above example, when ./ifile get executed, system actually run:
/bin/echo ./ifile

That's why we have above output.

Note: system has size limits of the first line of interpreter file.

2. Example

echoall.c:
 #include<stdio.h>  
 #include<stdlib.h>  
   
 int main(int argc, char* argv[])  
 {  
  int i;  
  for(i = 0; i < argc; i++) {  
   printf("argv[%d]: %s\n", i, argv[i]);  
  }  
   
  exit(0);  
 }  

testIntScript:
 #! /home/ubuntu/echoall.out foo bar  

testint.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<sys/wait.h>  
 #include<unistd.h>  
   
 int main(int argc, char* argv[])  
 {  
  pid_t pid;  
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if (pid == 0) {  
   if(execl("/home/ubuntu/testIntScript", "First arg", "Second Arg", (char*)0) < 0) {  
    printf("execl error!\n");  
    exit(2);  
   }  
  }  
   
  if(waitpid(pid, NULL, 0) < 0) {  
   printf("waitpid error!\n");  
   exit(3);  
  }  
   
  exit(0);  
 }  

shell:
Note: when calling the execl, the 1st arg is the interpreter file itself, 2nd arg is "all optional arguments"(foo bar), third arg is the script file, remaining arguments are from "Second Arg", "First arg" is ignored in this case.

So the real command getting run in this case is:
/home/ubuntu/echoall.out "foo bar" /home/ubuntu/testIntScript "Second Arg"
 ubuntu@ip-172-31-23-227:~$ ./testint.out  
 argv[0]: /home/ubuntu/echoall.out  
 argv[1]: foo bar  
 argv[2]: /home/ubuntu/testIntScript  
 argv[3]: Second Arg  

3. awk

awkscript:
 #! /usr/bin/awk -f  
 BEGIN {  
    for(i = 0; i < ARGC; i++)  
      printf("ARGV[%d] = %s\n", i, ARGV[i]);  
    exit  
 }  

shell:
The actual command getting run is :
/usr/bin/awk -f ./awkscript Hello world!
So it will have following output.
 ubuntu@ip-172-31-23-227:~$ ./awkscript Hello world!  
 ARGV[0] = awk  
 ARGV[1] = Hello  
 ARGV[2] = world!  

Efficiency gain with interpreter file, compared to following file:
awkShell:
 awk 'BEGIN {  
   for (i = 0; i < ARGC; i++)  
    printf "ARGV[%d] = %s\n", i, ARGV[i]  
    exit  
 }' $*  

shell:
Shell will firstly use execl to run the file "./awkShell", but it is not machine executable file. And then, it will turn to use the "/bin/sh" to run the file, with fork, exec and exit. Compared to interpreter file, it has more overhead time cost.
 ubuntu@ip-172-31-23-227:~$ ./awkShell Hello world!  
 ARGV[0] = awk  
 ARGV[1] = Hello  
 ARGV[2] = world!  

Tuesday, September 16, 2014

Unix Prog: Change User/Group IDs

1. setuid, setgid

We can use system calls: setuid, setgid to change process user ids and group ids.

System Definition:
If the user is not super user, the user id getting set up needs to be either real user id or saved set-user-id.
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h  
 ......  
 /* Set the user ID of the calling process to UID.  
   If the calling process is the super-user, set the real  
   and effective user IDs, and the saved set-user-ID to UID;  
   if not, the effective user ID is set to UID. */  
 extern int setuid (__uid_t __uid) __THROW __wur;  
 ......  
 /* Set the group ID of the calling process to GID.  
   If the calling process is the super-user, set the real  
   and effective group IDs, and the saved set-group-ID to GID;  
   if not, the effective group ID is set to GID. */  
 extern int setgid (__gid_t __gid) __THROW __wur;  
 ......  

Note: After running exec, the saved_set_user_id is copied from the effective user id.

Example:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 int main(int argc, char* argv[])  
 {  
  // 1000 has to either be real user id or saved set-user-id  
  if(setuid(1000) < 0) {  
   printf("setuid error!\n");  
   exit(1);  
  }  
  printf("current effective id: %d\n", geteuid());  
  printf("current real user id: %d\n", getuid());  
   
  exit(0);  
 }  

shell:
Since current user id is 1000, so we can feel free to change effective user id to real user id. If we are not super user, we can only change effective user id to either real user id or saved set user id.
 ubuntu@ip-172-31-23-227:~$ ./proc.out  
 current effective id: 1000  
 current real user id: 1000  

2. man function workflow
1) man normally has set-user-id bit on, so when user urn the man program, process has:
real-user-id: our user id
effective-user-id: man
saved set-user-id: man

2) man access its own files

3) before man run commands on our behalf, it will call setuid to change effective user id(assume we are not the super user)
real-user-id: our user id
effective-user-id: our user id
saved set-user-id: man

4) man run commands on our behalf with our user id, so it has enough access privilege to access our files

5) man run setuid again to change effective user id back to man, since saved set-user-id is man, so this operation is successful.
real-user-id: our user id
effective-user-id: man
saved set-user-id: man

3. setreuid, setregid

setreuid, setregid can be used to change current process's real user id, effective user id or real group id, effective group id.
Without super user privilege, we can only change effective user id, to either real user id or saved set-user-id.

System Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h  
 ......  
 /* Set the real user ID of the calling process to RUID,  
   and the effective user ID of the calling process to EUID. */  
 extern int setreuid (__uid_t __ruid, __uid_t __euid) __THROW __wur;  
 ......  
 /* Set the real group ID of the calling process to RGID,  
   and the effective group ID of the calling process to EGID. */  
 extern int setregid (__gid_t __rgid, __gid_t __egid) __THROW __wur;  
 ......  

proc.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 int main(int argc, char* argv[])  
 {  
  // If changing the real user id to other id(not our id), or change  
  // effective user id to the id which is not real user id or saved  
  // set-user-id, we need the super user privilege  
  if(setreuid(2, 2) < 0) {  
   printf("setreuid error!\n");  
   exit(1);  
  }  
   
  printf("Current effective id: %d\n", geteuid());  
  printf("Current real user id: %d\n", getuid());  
  exit(0);  
 }  

shell:
1) Because current user id is 1000, without super user privilege we can't change the real user id to 2
2) Run the program with super user privilege, this time it is successful.
 ubuntu@ip-172-31-23-227:~$ ./proc.out  
 setreuid error!  
 ubuntu@ip-172-31-23-227:~$ sudo ./proc.out  
 Current effective id: 2  
 Current real user id: 2  

4. seteuid, setegid
seteuid, setegid can be used to change process's effective user/group id only.
Again, without super-user privilege, we can only change effective user id to either real user id or saved set-user-id.

system definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h  
 ......  
 /* Set the effective user ID of the calling process to UID. */  
 extern int seteuid (__uid_t __uid) __THROW __wur;  
 ......  
 /* Set the effective group ID of the calling process to GID. */  
 extern int setegid (__gid_t __gid) __THROW __wur;  
 ......  

proc.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 int main(int argc, char* argv[])  
 {  
  if(seteuid(2) < 0) {  
   printf("setreuid error!\n");  
   exit(1);  
  }  
   
  printf("Current effective id: %d\n", geteuid());  
  printf("Current real user id: %d\n", getuid());  
  exit(0);  
 }  

shell:
1) current user id is 1000, without super-user privilege, we can't change the current user id to 2(not real user id, not saved-set-user-id)
2) with super user privilege, we can change the effective user id to 2, but real user id is not changed, it is still 0(super user).
 ubuntu@ip-172-31-23-227:~$ ./proc.out  
 setreuid error!  
 ubuntu@ip-172-31-23-227:~$ sudo ./proc.out  
 Current effective id: 2  
 Current real user id: 0  

Monday, September 15, 2014

Unix Prog: exec functions

1. exec functions
When a process calls one of exec functions, the process is completely replaced by the new program, and the new program starts executing from the main function. exec merely replaces the current process, text, data, heap and stack segments while retaining the process ID.

System Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h  
 ......  
 /* Replace the current process, executing PATH with arguments ARGV and  
   environment ENVP. ARGV and ENVP are terminated by NULL pointers. */  
 extern int execve (const char *__path, char *const __argv[],  
           char *const __envp[]) __THROW __nonnull ((1, 2));  
 ......  
 /* Execute PATH with arguments ARGV and environment from `environ'. */  
 extern int execv (const char *__path, char *const __argv[])  
    __THROW __nonnull ((1, 2));  
   
 /* Execute PATH with all arguments after PATH until a NULL pointer,  
   and the argument after that for environment. */  
 extern int execle (const char *__path, const char *__arg, ...)  
    __THROW __nonnull ((1, 2));  
   
 /* Execute PATH with all arguments after PATH until  
   a NULL pointer and environment from `environ'. */  
 extern int execl (const char *__path, const char *__arg, ...)  
    __THROW __nonnull ((1, 2));  
   
 /* Execute FILE, searching in the `PATH' environment variable if it contains  
   no slashes, with arguments ARGV and environment from `environ'. */  
 extern int execvp (const char *__file, char *const __argv[])  
    __THROW __nonnull ((1, 2));  
   
 /* Execute FILE, searching in the `PATH' environment variable if  
   it contains no slashes, with all arguments after FILE until a  
   NULL pointer and environment from `environ'. */  
 extern int execlp (const char *__file, const char *__arg, ...)  
    __THROW __nonnull ((1, 2));  
 ......  

If the execution file is not executable, then it will launch /bin/sh to run against the file.

If we don't pass the environment variable then it will use global variable "environ" of the current process.

For each file descriptor of current process, if its FD_CLOEXEC flag is on, it will be closed after running the exec, otherwise it will keep open.

Real user id and real group id will keep same, but the effective user id and effective group id will depend on the "set_user_id", "set_group_id" flag of executable file.

Only execve is the system call, the calling relationship of different functions:
execlp -(build argv)-> execvp
execl -(build argv)-> execv
execle -(build argv)-> execve
execvp->execv->execve(system call)

2. Example

echoall.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 int main(int argc, char* argv[])  
 {  
  int i;  
  char **ptr;  
   
  for(i = 0; i < argc; i++)  
   printf("argv[%d]: %s\n", i, argv[i]);  
   
  for(ptr = __environ; *ptr != 0; ptr++)  
   printf("%s\n", *ptr);  
   
  exit(0);  
 }  

shell:
 ubuntu@ip-172-31-23-227:~$ ./echoall.out Hello world  
 argv[0]: ./echoall.out  
 argv[1]: Hello  
 argv[2]: world  
 XDG_SESSION_ID=60  
 TERM=xterm  
 SHELL=/bin/bash  
 SSH_CLIENT=67.243.178.100 50301 22  
 SSH_TTY=/dev/pts/0  
 USER=ubuntu  
 ......  

proc.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
   
 char *env_init[]={"USER=unknown", "PATH=/tmp", NULL};  
   
 int main(int argc, char* argv[])  
 {  
  pid_t pid;  
   
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if (pid == 0) {  
   // Note: we have to convert 0 to (char*), if in current system  
   // memory usage for int and pointer are different, then it would  
   // cause error  
   if(execle("./echoall.out", "First Arg", "Second Arg", (char*)0, env_init) < 0) {  
    printf("execle error!\n");  
    exit(3);  
   }  
   exit(0);  
  }  
   
  // Wait for the child process finish firstly  
  if(waitpid(pid, NULL, 0) < 0) {  
   printf("waitpid error!\n");  
   exit(2);  
  }  
   
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if (pid == 0) {  
   if(execlp("echoall.out", "First Arg", "Second Arg", (char*) 0) < 0) {  
    printf("execlp error!\n");  
    exit(3);  
   }  
  }  
   
  // Wait for the child process finish firstly  
  if(waitpid(pid, NULL, 0) < 0) {  
   printf("waitpid error!\n");  
   exit(2);  
  }  
   
  exit(0);  
 }  

shell:
Note:
If running the program from the shell, the shell will put first argument as the program name automatically. In this case ,we just provide the "First Arg", "Second Arg" as two arguments, so we can't see the program name as argument.
 ubuntu@ip-172-31-23-227:~$ ./proc.out  
 argv[0]: First Arg  
 argv[1]: Second Arg  
 USER=unknown  
 PATH=/tmp  
 argv[0]: First Arg  
 argv[1]: Second Arg  
 XDG_SESSION_ID=60  
 TERM=xterm  
 SHELL=/bin/bash  
 SSH_CLIENT=67.243.178.100 50301 22  
 SSH_TTY=/dev/pts/0  
 USER=ubuntu  
 ......  

Sunday, September 14, 2014

Unix Prog: Race Condition

1. Race Condition

A race condition occurs when multiple processes are trying to do sth with shared data and the final outcome depends on the order in which the processes run.

In order to avoid the race condition, we need the signal mechanism to help process communication with each other.

2. Race Condition Example

proc.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<sys/wait.h>  
   
 void charatatime(char *str)  
 {  
  char *ptr;  
  int c;  
   
  setbuf(stdout, NULL);  
  for(ptr = str; (c=*ptr++)!=0;)  
   putc(c, stdout);  
 }  
   
 int main(int argc, char* argv[])  
 {  
  pid_t pid;  
   
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if (pid == 0) {  
   int i;  
   for(i=0; i<10000; i++)  
    charatatime("output from a child!\n");  
  } else {  
   int i;  
   for(i=0; i<10000;i++)  
    charatatime("output from a parent!\n");  
  }  
   
  exit(0);  
 }  

shell:
Because we let parent process and child process output at the same time without setting any buffer. So we can see its output getting content missed up.
 ubuntu@ip-172-31-23-227:~$ ./proc.out >output.txt  
 ubuntu@ip-172-31-23-227:~$ grep -v "output from" output.txt  
 ouild!  
 out a child!  
  child!  
 ouparent!  
 ......  

3. Remove Race Condition Example

proc.c:
The only difference is: the parent process wait for child process finish firstly before outputting.
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<sys/wait.h>  
   
 void charatatime(char *str)  
 {  
  char *ptr;  
  int c;  
   
  setbuf(stdout, NULL);  
  for(ptr = str; (c=*ptr++)!=0;)  
   putc(c, stdout);  
 }  
   
 int main(int argc, char* argv[])  
 {  
  pid_t pid;  
   
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if (pid == 0) {  
   int i;  
   for(i=0; i<10000; i++)  
    charatatime("output from a child!\n");  
  } else {  
   // Before parent process do sth, wait for child finish firstly  
   if(wait(NULL) < 0) {  
    printf("wait error!\n");  
    exit(2);  
   }  
   int i;  
   for(i=0; i<10000;i++)  
    charatatime("output from a parent!\n");  
  }  
   
  exit(0);  
 }  

shell:
 ubuntu@ip-172-31-23-227:~$ ./proc.out > output.txt  
 ubuntu@ip-172-31-23-227:~$ grep -v "output from" output.txt  
 ubuntu@ip-172-31-23-227:~$  

Unix Prog: Wait for Process -- waitid, wait3, wait4

1. waitid

System Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/x86_64-linux-gnu/sys/wait.h  
 ......  
 /* Wait for a childing matching IDTYPE and ID to change the status and  
   place appropriate information in *INFOP.  
   If IDTYPE is P_PID, match any process whose process ID is ID.  
   If IDTYPE is P_PGID, match any process whose process group is ID.  
   If IDTYPE is P_ALL, match any process.  
   If the WNOHANG bit is set in OPTIONS, and that child  
   is not already dead, clear *INFOP and return 0. If successful, store  
   exit code and status in *INFOP.  
   
   This function is a cancellation point and therefore not marked with  
   __THROW. */  
 extern int waitid (idtype_t __idtype, __id_t __id, siginfo_t *__infop,  
           int __options);  
 ......  
 ubuntu@ip-172-31-23-227:~$ less /usr/include/x86_64-linux-gnu/bits/waitflags.h  
 ......  
 /* Bits in the third argument to `waitpid'. */  
 #define WNOHANG     1    /* Don't block waiting. */  
 #define WUNTRACED    2    /* Report status of stopped children. */  
 ......  
 /* Bits in the fourth argument to `waitid'. */  
 #define WSTOPPED    2    /* Report stopped child (same as WUNTRACED). */  
 #define WEXITED     4    /* Report dead child. */  
 #define WCONTINUED   8    /* Report continued child. */  
 #define WNOWAIT     0x01000000 /* Don't reap, just poll status. */  
 ......  
 #define __WNOTHREAD   0x20000000 /* Don't wait on children of other threads  
                    in this group */  
 #define __WALL     0x40000000 /* Wait for any child. */  
 #define __WCLONE    0x80000000 /* Wait for cloned process. */  
 ......  
 typedef enum  
 {  
  P_ALL,        /* Wait for any child. */  
  P_PID,        /* Wait for specified process. */  
  P_PGID        /* Wait for members of process group. */  
 } idtype_t;  
 ......  

proc.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<sys/wait.h>  
   
 int main(int argc, char* argv[])  
 {  
  pid_t pid;  
   
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if(pid == 0) {  
   printf("child process output!\n");  
   sleep(2);  
   exit(0);  
  }  
   
  siginfo_t infop;  
  if(waitid(P_PID, pid, &infop, WEXITED) < 0) {  
   printf("waitid error!\n");  
   exit(1);  
  }  
   
  printf("child terminated. parent process output!\n");  
  exit(0);  
 }  

shell:
 ubuntu@ip-172-31-23-227:~$ ./proc.out  
 child process output!  
 child terminated. parent process output!  

2. wait3 and wait4

The difference of wait3 and wait4 is: it provides the argument "struct rusage" to return the resource usage information for this process.

System Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/x86_64-linux-gnu/sys/wait.h  
 ......  
 /* Wait for a child to exit. When one does, put its status in *STAT_LOC and  
   return its process ID. For errors return (pid_t) -1. If USAGE is not  
   nil, store information about the child's resource usage there. If the  
   WUNTRACED bit is set in OPTIONS, return status for stopped children;  
   otherwise don't. */  
 extern __pid_t wait3 (__WAIT_STATUS __stat_loc, int __options,  
            struct rusage * __usage) __THROWNL;  
 #endif  
   
 #ifdef __USE_BSD  
 /* PID is like waitpid. Other args are like wait3. */  
 extern __pid_t wait4 (__pid_t __pid, __WAIT_STATUS __stat_loc, int __options,  
            struct rusage *__usage) __THROWNL;  
 ......  

proc.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<sys/wait.h>  
 #include<sys/resource.h>  
   
 int main(int argc, char* argv[])  
 {  
  pid_t pid;  
   
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if(pid == 0) {  
   printf("child process output!\n");  
   sleep(2);  
   exit(0);  
  }  
   
  struct rusage use;  
  int status;  
  if(wait4(pid, &status, 0, &use) < 0) {  
   printf("wait4 error!\n");  
   exit(1);  
  }  
   
  printf("child terminated. parent process output!\n");  
  exit(0);  
 }  

shell:
 ubuntu@ip-172-31-23-227:~$ ./proc.out  
 child process output!  
 child terminated. parent process output!  

Unix Prog: Wait for Process -- wait, waitpid

1. wait, waitpid

Whenever a child process is terminated, the kernel will send one asynchronous signal to the parent process. The parent process can choose to :
1) ignore the signal
2) provide the signal handler

By calling wait, waitpid, the parent process can:
1) block if all of its child processes are still running
2) return immediately with the termination status of the child, if a child has terminated and is waiting for its termination status to be fetched.
3) return immediately with the error, if the parent process doesn't have any child processes.

Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/x86_64-linux-gnu/sys/wait.h  
 ......  
 # define WEXITSTATUS(status)  __WEXITSTATUS (__WAIT_INT (status))  
 # define WTERMSIG(status)    __WTERMSIG (__WAIT_INT (status))  
 # define WSTOPSIG(status)    __WSTOPSIG (__WAIT_INT (status))  
 # define WIFEXITED(status)   __WIFEXITED (__WAIT_INT (status))  
 # define WIFSIGNALED(status)  __WIFSIGNALED (__WAIT_INT (status))  
 # define WIFSTOPPED(status)   __WIFSTOPPED (__WAIT_INT (status))  
 # ifdef __WIFCONTINUED  
 # define WIFCONTINUED(status) __WIFCONTINUED (__WAIT_INT (status))  
 # endif  
 ......  
 #ifdef __USE_BSD  
 # define WCOREFLAG       __WCOREFLAG  
 # define WCOREDUMP(status)   __WCOREDUMP (__WAIT_INT (status))  
 # define W_EXITCODE(ret, sig)  __W_EXITCODE (ret, sig)  
 # define W_STOPCODE(sig)    __W_STOPCODE (sig)  
 #endif  
   
 /* Wait for a child to die. When one does, put its status in *STAT_LOC  
   and return its process ID. For errors, return (pid_t) -1.  
   
   This function is a cancellation point and therefore not marked with  
   __THROW. */  
 extern __pid_t wait (__WAIT_STATUS __stat_loc);  
 ......  
 /* Wait for a child matching PID to die.  
   If PID is greater than 0, match any process whose process ID is PID.  
   If PID is (pid_t) -1, match any process.  
   If PID is (pid_t) 0, match any process with the  
   same process group as the current process.  
   If PID is less than -1, match any process whose  
   process group is the absolute value of PID.  
   If the WNOHANG bit is set in OPTIONS, and that child  
   is not already dead, return (pid_t) 0. If successful,  
   return PID and store the dead child's status in STAT_LOC.  
   Return (pid_t) -1 for errors. If the WUNTRACED bit is  
   set in OPTIONS, return status for stopped children; otherwise don't.  
   
   This function is a cancellation point and therefore not marked with  
   __THROW. */  
 extern __pid_t waitpid (__pid_t __pid, int *__stat_loc, int __options);  
 ......  

2. wait example
proc.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<sys/wait.h>  
 #include<unistd.h>  
   
 void pr_exit(int status)  
 {  
  if(WIFEXITED(status)) {  
   printf("normal termination: exit status = %d\n", WEXITSTATUS(status));  
  }  
  else if(WIFSIGNALED(status)) {  
   printf("abnormal termination: exit status = %d%s\n",  
       WTERMSIG(status), WCOREDUMP(status)? "(core file generated)" : "");  
  }  
  else if (WIFSTOPPED(status)) {  
   printf("child stopped, signal number = %d\n", WSTOPSIG(status));  
  }  
 }  
   
 int main(int argc, char* argv[])  
 {  
  pid_t pid;  
  int status;  
   
  // Launch the first child  
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if (pid == 0)  
   exit(7);  
   
  // Fetch the termination status of first child  
  if(wait(&status) != pid) {  
   printf("wait error!\n");  
   exit(2);  
  }  
  pr_exit(status);  
   
  // Launch the second child  
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if(pid == 0)  
   abort();  // Generate the SIGABRT  
   
  // Fetch the termination status of the second child  
  if(wait(&status) != pid) {  
   printf("wait error!\n");  
   exit(2);  
  }  
  pr_exit(status);  
   
  // Launch the third child  
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if(pid == 0)  
   status /= 0; // Generate the SIGFPE  
   
  // Fetch the termination status of third child  
  if(wait(&status) != pid) {  
   printf("wait error!\n");  
   exit(2);  
  }  
  pr_exit(status);  
   
  exit(0);  
 }  

shell:
 ubuntu@ip-172-31-23-227:~$ ./proc.out  
 normal termination: exit status = 7  
 abnormal termination: exit status = 6(core file generated)  
 abnormal termination: exit status = 8(core file generated)  

3. waitpid example:

waitflags definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/x86_64-linux-gnu/bits/waitflags.h  
 ......  
 /* Bits in the third argument to `waitpid'. */  
 #define WNOHANG     1    /* Don't block waiting. */  
 #define WUNTRACED    2    /* Report status of stopped children. */  
 ......  

proc.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<sys/wait.h>  
   
 int main(int argc, char* argv[])  
 {  
  pid_t pid;  
   
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(1);  
  }  
  else if (pid == 0) {  
   /* first child process */  
   if ((pid = fork()) < 0) {  
    printf("fork error!\n");  
    exit(1);  
   }  
   else if (pid > 0) {  
    sleep(1);  
    printf("first child output!\n");  
    exit(0); // exit from first child process  
   }  
   
   // 2nd child process sleep for 2 seconds to let 1st  
   // child process exit, at this time, its parent process  
   // becomes "init" (process id: 1)  
   sleep(2);  
   printf("second child, parent pid = %d\n", getppid());  
   exit(0);  
  }  
   
  // Original Process: Wait for process id of first child  
  // Since first child will sleep for 1 second, and we don't setup  
  // WNOHANG flag as 3rd argument, so it will just block here to wait  
  // for first child  
  printf("before waitpid:\n");  
  if(waitpid(pid, NULL, 0) != pid) {  
   printf("waitpid error!\n");  
   exit(2);  
  }  
  printf("after waitpid:\n");  
   
  exit(0);  
 }  

shell:
 ubuntu@ip-172-31-23-227:~$ ./proc.out  
 before waitpid:  
 first child output!  
 after waitpid:  
 ubuntu@ip-172-31-23-227:~$ second child, parent pid = 1  

Unix Prog: Exit Process -- exit

1. System Definition

 ubuntu@ip-172-31-23-227:~$ less /usr/include/stdlib.h  
 ......  
 /* Call all functions registered with `atexit' and `on_exit',  
   in the reverse of the order in which they were registered,  
   perform stdio cleanup, and terminate program execution with STATUS. */  
 extern void exit (int __status) __THROW __attribute__ ((__noreturn__));  
 ......  
 /* Terminate the program with STATUS without calling any of the  
   functions registered with `atexit' or `on_exit'. */  
 extern void _Exit (int __status) __THROW __attribute__ ((__noreturn__));  
 ......  
 ubuntu@ip-172-31-23-227:~$ less /usr/include/unistd.h  
 ......  
 /* Terminate program execution with the low-order 8 bits of STATUS. */  
 extern void _exit (int __status) __attribute__ ((__noreturn__));  
 ......  

2. 5 normal ways to exit process
1) return from main function. This is equivalent to "exit".
2) Call the exit function, as described above.
3) Call the _Exit or _exit function
4) Call the return from the start routine of last thread in the process, the return value of the thread is not taken as process's return value.
5) Call the pthread_exit function from the last thread in the process.

3. 3 Abnormal ways to exit function
1) Call abort, generating the SIGABRT signal
2) When the process receive signal generated by itself or other process
3) The last thread responds to a cancellation request.

4. Kernel Code to terminate process
Regardless how the process is terminated, the kernel code will close all open file descriptors, release the memory etc.

5. Exit Status
For all terminated process, we want them to notify their parents how they are terminated. This is done by passing the argument to exit, _exit, _Exit. Note, the argument we pass in is exit status, it will be converted to termination status

6. Parent Process terminated
What if parent process get terminated before the child process. Then kernel will scan through all active processes who are the children of terminating process, if yes, it will assign init process(id 1) as its new parent process.

7. Child Process terminated
What if child process get terminated before the parent process. The kernel will keep a small amount of child process's information like: process id, termination status, and CPU time taken by the process. Then parent process could use wait/waitpid function to retrieve the child process information.

8. Zombie Process
For child process who has terminated but its parent has not yet waited for it, it is called a zombie process.

9. Process inherited from init terminated
The init process is designed to use wait function to fetch the termination status whenever the process inherited from init get terminated.