/*
 * util.c
 *
 * Copyright (c) 1990, 1991, John W. Eaton.
 *
 * You may distribute under the terms of the GNU General Public
 * License as specified in the file COPYING that comes with the man
 * distribution.  
 *
 * John W. Eaton
 * jwe@che.utexas.edu
 * Department of Chemical Engineering
 * The University of Texas at Austin
 * Austin, Texas  78712
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <jerror.h>

#include "util.h"
#include "gripedefs.h"

int debug = 0;
char * progname = 0;

/*
 * Extract last element of a name like /foo/bar/baz.
 */
char *
mkprogname (char *s) {
     char *t;

     t = strrchr (s, '/');
     if (t == (char *)NULL)
	  t = s;
     else
	  t++;

     return my_strdup (t);
}

/*
 * Is file a nonempty and newer than file b?
 *
 * case:
 *
 *   a newer than b              returns    1
 *   a older than b              returns    0
 *   stat on a fails or a empty  returns   -1
 *   stat on b fails or b empty  returns   -2
 *   both fail or empty  	 returns   -3
 */
int
is_newer (char *fa, char *fb) {
     struct stat fa_sb;
     struct stat fb_sb;
     register int fa_stat;
     register int fb_stat;
     register int status = 0;

     fa_stat = stat (fa, &fa_sb);
     if (fa_stat != 0 || fa_sb.st_size == 0)
	  status = 1;

     fb_stat = stat (fb, &fb_sb);
     if (fb_stat != 0 || fb_sb.st_size == 0)
	  status |= 2;

     if (status != 0)
	  return -status;

     return (fa_sb.st_mtime > fb_sb.st_mtime);
}

int ruid, rgid, euid, egid, suid;

void
get_permissions (void) {
     ruid = getuid();
     euid = geteuid();
     rgid = getgid();
     egid = getegid();
     suid = (ruid != euid || rgid != egid);
}

void
no_privileges (void) {
     if (suid) {
#ifndef __CYGWIN32__
	  setreuid(ruid, ruid);
	  setregid(rgid, rgid);
#endif
	  suid = 0;
     }
}

/*
 * Unfortunately, Linux libc system() ignores SIGINT and SIGQUIT
 * The consequence is that "man -K" cannot be interrupted.
 * Here a private copy that doesn't fiddle with signals.
 * (But see below.)
 */
static int
system0 (char *command) {
     int pid, pid2, status;

     pid = fork();
     if (pid == -1) {
	  perror(progname);
	  fatal (CANNOT_FORK, command);
     }
     if (pid == 0) {
	  char *argv[4];
	  argv[0] = "sh";
	  argv[1] = "-c";
	  argv[2] = command;
	  argv[3] = 0;
	  execv("/bin/sh", argv);	/* was: execve(*,*,environ); */
	  exit(127);
     }
     do {
	  pid2 = wait(&status);
	  if (pid2 == -1)
	       return -1;
     } while(pid2 != pid);
     return status;
}

/*
 * What to do upon an interrupt?  Experience shows that
 * if we exit immediately, sh notices that its child has
 * died and will try to fiddle with the tty.
 * Simultaneously, also less will fiddle with the tty,
 * resetting the mode before exiting.
 * This leads to undesirable races. So, we catch SIGINT here
 * and exit after the child has exited.
 */
static int interrupted = 0;
static void catch_int(int a) {
	interrupted = 1;
}

static int
system1 (char *command) {
     void (*prev_handler)(int) = signal (SIGINT,catch_int);
     int ret = system0(command);
     if (interrupted)
	  exit(1);
     signal(SIGINT,prev_handler);
     return ret;
}

static int
my_system (char *command) {
     int pid, pid2, status, stat;

     if (!suid)
	  return system1 (command);

#ifdef _POSIX_SAVED_IDS

     /* we need not fork */
     setuid(ruid);
     setgid(rgid);
     status = system1(command);
     setuid(euid);
     setgid(egid);
     return (WIFEXITED(status) ? WEXITSTATUS(status) : 127);
#endif

     fflush(stdout); fflush(stderr);
     pid = fork();
     if (pid == -1) {
	  perror(progname);
	  fatal (CANNOT_FORK, command);
     }
     if (pid == 0) {
	  setuid(ruid);
	  setgid(rgid);
	  status = system1 (command);
	  exit(WIFEXITED(status) ? WEXITSTATUS(status) : 127);
     }
     pid2 = wait (&stat);
     if (pid2 == -1) {
	  perror(progname);
	  fatal (WAIT_FAILED, command); 	/* interrupted? */
     }
     if (pid2 != pid)
	  fatal (GOT_WRONG_PID);
     if (WIFEXITED(stat) && WEXITSTATUS(stat) != 127)
	  return WEXITSTATUS(stat);
     fatal (CHILD_TERMINATED_ABNORMALLY, command);
     return -1;			/* not reached */
}

FILE *
my_popen(const char *command, const char *type) {
     FILE *r;

     if (!suid)
	  return popen(command, type);

#ifdef _POSIX_SAVED_IDS
     setuid(ruid);
     setgid(rgid);
     r = popen(command, type);
     setuid(euid);
     setgid(egid);
     return r;
#endif

     no_privileges();
     return popen(command, type);
}

/*
 * Attempt a system () call.
 */
int
do_system_command (char *command, int silent) {
     int status = 0;

     /*
      * If we're debugging, don't really execute the command -- you never
      * know what might be in that mangled string :-O.
      */
     if (debug == 1) 
	  gripe (NO_EXEC, command);
     else
	  status = my_system (command);

     if (status && !silent)
	  gripe (SYSTEM_FAILED, command, status);

     return status;
}

char *
my_malloc (int n) {
    char *s = (char*) malloc(n);
    if (!s)
	fatal (OUT_OF_MEMORY, n);
    return s;
}

char *
my_strdup (char *s) {
    char *t = my_malloc(strlen(s) + 1);
    strcpy(t, s);
    return t;
}

/******************* modifyed by Wang XinKai ,1999.3.19 *********/
char *getmsg(int n)
{
	static char buffer[100];
	buffer[0] = 0;
	//sprintf(buffer,"error, errorcode = %d\n",n);
	return buffer;
}

void
gripe (int n, ...) {
    va_list p;

    va_start(p, n);
    vfprintf (stderr, getmsg(n), p);
    va_end(p);
    fflush (stderr);
}

void
fatal (int n, ...) {
    va_list p;
    fprintf (stderr, "%s: ", progname);
    va_start(p, n);
    vfprintf (stderr, getmsg(n), p);
    va_end(p);
    exit (1);
}
/************************** modify end ************************/
