/*
 * File:	io.c
 *
 * Author:	Ulli Horlacher (framstag@rus.uni-stuttgart.de)
 *
 * History:	
 *
 *  1995-08-11  Framstag	initial version
 *  1996-04-23  Framstag	added file copying function
 *  1996-05-02  Framstag	corrected file creating bug in fcopy()
 *  1996-06-24  Framstag	enhanced fcopy() to copy to stdout
 *  1997-02-02  Framstag	improved reliability for fcopy() with NFS
 *  1997-02-24  Framstag	sprintf() -> snprintf()
 *  1997-05-05  Framstag	better IRIX support (blksize)
 *  1997-12-09	Framstag	added whereis()
 *  2001-01-10	Framstag	added rfopen()
 *  2001-02-04	Framstag	added mktmpdir() and rmtmpdir()
 *  2001-02-16	Framstag	fixed cleanup loop
 *  2002-08-04	Framstag	moved spawn() from receice.c
 *  2002-12-19	Framstag	rmtmpdir: ignore non-existing tmpdir
 *  2005-05-31	Framstag        added largefile support
 *  				enhanced fcopy performance (fixed blksize bug)
 *  2005-11-10	Framstag        added vsystem()
 *  2005-11-11	Framstag        added vpopen()
 *
 * Socket read and write routines and file copy function of the
 * sendfile package.
 *
 * Copyright © 1995-2005 Ulli Horlacher
 * This file is covered by the GNU General Public License
 */

#include "config.h"		/* various #defines */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include "io.h"			/* (socket) read/write */
#include "net.h"		/* the network routines */
#include "string.h"		/* extended string functions */
#include "message.h"		/* information, warning and error messages */

/*
 * readn - read n bytes from network socket
 *
 * INPUT:  fd     - socket file descriptor
 *         ptr    - empty string
 *         nbytes - number of bytes to read
 *
 * RETURN: number of actual read bytes
 *
 * this function is derived from example code from
 * "Unix Networking Programming" by W. R. Stevens
 */
int readn(int fd, char *ptr, int nbytes) {
  int nleft, nread;

  nleft=nbytes;
  while (nleft>0) {
    nread=read(fd, ptr, nleft);
    if (nread<0)
      return(nread);
    else
      if (nread==0) break;
    nleft-=nread;
    ptr+=nread;
  }
  return(nbytes-nleft);
}


/*
 * writen - write n bytes to network socket
 *
 * INPUT:  fd     - socket file descriptor
 *         ptr    - string to send
 *         nbytes - number of bytes to send
 *
 * RETURN: number of actual written bytes
 *
 * this function is derived from example code from
 * "Unix Networking Programming" by W. R. Stevens
 */
int writen(int fd, char *ptr, int nbytes) {
  int nleft, nwritten;

  nleft=nbytes;
  while (nleft>0) {
    nwritten=write(fd, ptr, nleft);
    if (nwritten<0) return(nwritten);
    nleft-=nwritten;
    ptr+=nwritten;
  }
  return(nbytes-nleft);
}


/*
 * fcopy - copy a file (copy to stdout if there is no destination filename)
 *
 * INPUT:  from     - source file
 *         to       - destination file
 *         mode     - file protection mode
 *
 * RETURN: 0 if ok, -1 on failure
 *
 */
int fcopy(const char *from, const char *to, mode_t mode) {
  int fdin, fdout;	/* file descriptor in/out */
  int 
    rbytes,		/* read bytes */
    wbytes;		/* written bytes */
  char 
    tmp[MAXLEN],	/* temporary string */
    *buf;		/* copy buffer */
  struct stat finfo;	/* information about a file */
  off_t
    fsize,		/* original file size */
    wtotal;		/* total read bytes */
  long
    blksize;		/* file system block size */

  wtotal=0;

  /* get the original file size */
  if (stat(from,&finfo)<0) {
    snprintf(MAXS(tmp),"cannot access '%s'",from);
    message("",'E',tmp);
    return(-1);
  }
  fsize=finfo.st_size;
#ifdef HAVE_ST_BLKSIZE
  blksize=finfo.st_blksize;
#else  
  blksize=1024;
#endif
  
      
  /* open source file */
  fdin=open(from,O_RDONLY|O_LARGEFILE,0);
  if (fdin<0) {
    snprintf(MAXS(tmp),"error opening '%s'",from);
    message("",'E',tmp);
    return(-1);
  }

  /* destination file specified? */
  if (*to) {
    
    /* open destination file */
    fdout=creat(to,mode);
    if (fdout<0) {
      snprintf(MAXS(tmp),"error creating '%s'",to);
      message("",'E',tmp);
      close(fdin);
      return(-1);
    }


    /* ANSI C can not dynamicly allocate with buf[blksize] */
    buf=(char *)malloc(blksize);
    if (!buf) message("",'F',"out of memory");

    /* read until EOF */
    while ((rbytes=read(fdin,buf,blksize)) > 0) {
     
      /* write to destination file */
      wbytes=write(fdout,buf,rbytes);
      if (wbytes!=rbytes) {
       
	/* write error */
	close(fdin);
	close(fdout);
	free(buf);
	snprintf(MAXS(tmp),"error writing '%s'",to);
	message("",'E',tmp);
	return(-1);
      }

      wtotal+=wbytes;
      
    }

    close(fdout);

  } else /* copy to stdout */ {
    
    /* ANSI C can not dynamicly allocate with buf[blksize] */
    buf=(char *)malloc(blksize);
    if (!buf) message("",'F',"out of memory");

    /* read until EOF */
    while ((rbytes=read(fdin,buf,blksize)) > 0) {
     
      /* write to stdout */
      wbytes=write(fileno(stdout),buf,rbytes);
      wtotal+=wbytes;
    }

  }

  close(fdin);
  free(buf);

  /* read error? */
  if (rbytes<0) {
    snprintf(MAXS(tmp),"error reading '%s'",from);
    message("",'E',tmp);
    return(-1);
  }

  /* count mismatch or read/write errors? */
  if (fsize!=wtotal) {
    errno=0;
    snprintf(MAXS(tmp),"wrong byte count for '%s'",from);
    message("",'E',tmp);
    return(-1);
  }
  
  return(0);
}


/*
 * whereis  - where is a program in the path
 *
 * INPUT:  prg     - program to look for
 * 
 * RETURN: NULL if not found else path to file
 */
char *whereis(char *prg) {
  int len;
  static char filepath[MAXLEN];	/* file with path */
  char *path;			/* $PATH */
  char *cp;
  
  /* when file contains a / test directly */
  if (strchr(prg,'/')) {
    if (access(prg,X_OK)==0)
      return(prg);
    else
      return(NULL);
  }
  
  len=strlen(prg);
  path=getenv("PATH");
  if (!path || !strchr(path,'/')) return(NULL);
  
  while (*path==':') path++;
  
  while (*path) {
    snprintf(filepath,MAXLEN-2-len,"%s",path);
    if ((cp=strchr(filepath,':'))) *cp=0;
    strcat(filepath,"/");
    strcat(filepath,prg);
    if (access(filepath,X_OK)==0) return(filepath);
    if ((cp=strchr(path,':'))) 
      path=cp+1;
    else
      return(NULL);
  }
  
  return(NULL);
  
}


/*
 * rfopen  - open a regular file
 *
 * INPUT:  file     - file name (with path)
 *	   mode	    - fopen mode 
 * 
 * RETURN: same like fopen(), but NULL if file is not a regular file
 */
FILE *rfopen(const char *file, const char *mode) {
  struct stat finfo;	/* information about a file */
  
  /* what kind of file ist it and do we have access at all? */
  if (stat(file,&finfo)<0) {
    
    /* no such file ==> open it! (mode can be "w" or "a+") */
    if (errno==ENOENT) 
      return(fopen(file,mode));
    else
      /* other error */
      return(NULL);
    
  } else {
    
    /* regular file? */
    if (finfo.st_mode&S_IFREG) {
      return(fopen(file,mode));
    } else {
      errno=EIO;
      return(NULL);
    }
    
  }
}


/* 
 * mktmpdir - create a new temporary directory
 * 
 * INPUT:  verbose - flag
 * 
 * RETURN: full path of the temporary directory
 * 
 * ENVIRONMENT: SF_TMPDIR, TMPDIR
 */
char *mktmpdir(int verbose) {
  char *cp;			/* a simple string pointer */
  char tmp[MAXLEN];		/* temporary string */
  static char tmpdir[FLEN];	/* directory for temporary files */
    
  /* get tmpdir base */
  if ((cp=getenv("SF_TMPDIR")))
    snprintf(tmpdir,FLEN-30,"%s",cp);
  else if ((cp=getenv("TMPDIR")))
    snprintf(tmpdir,FLEN-30,"%s",cp);
  else
    strcpy(tmpdir,"/tmp");
  
  snprintf(tmp,30,"/sf_%u.tmp",(unsigned int)(time(NULL)*getpid()));
  strcat(tmpdir,tmp);
  
  if (mkdir(tmpdir,S_IRWXU)<0 || chmod(tmpdir,S_IRWXU)<0) {
    snprintf(MAXS(tmp),"cannot create tmpdir %s",tmpdir);
    message("",'F',tmp);
  }
  
  if (verbose) {
    snprintf(MAXS(tmp),"directory for temporary files is: %s",tmpdir);
    message("",'I',tmp);
  }
  
  return(tmpdir);
}


/* 
 * rmtmpdir - delete the temporary directory
 * 
 * INPUT: full path of the temporary directory
 */
void rmtmpdir(char *tmpdir) {
  char cwd[FLEN];		/* current directory */
  char tmp[MAXLEN];		/* temporary string */
  struct dirent *dire;          /* directory entry */
  DIR *dp=NULL;			/* directory pointer */

  if (!tmpdir || !*tmpdir) return;
  
  if (!getcwd(MAXS(cwd))) strcpy(cwd,"/tmp");

  /* open dir */
  if (chdir(tmpdir) < 0 || !(dp=opendir(tmpdir))) {
    /*
    snprintf(MAXS(tmp),"cleanup: cannot open %s",tmpdir);
    message("",'X',tmp);
     */
    return;
  }
  
  while ((dire=readdir(dp))) {
      
    /* skip . and .. */
    if (strcmp(dire->d_name,".")==0 || strcmp(dire->d_name,"..")==0) continue;

    /* delete file */
    if (unlink(dire->d_name) < 0) {
      snprintf(MAXS(tmp),"cannot remove %s/%s",tmpdir,dire->d_name);
      message("",'W',tmp);
    }
    
  }

  chdir(cwd);
  if (rmdir(tmpdir) < 0) {
    snprintf(MAXS(tmp),"cannot remove %s",tmpdir);
    message("",'X',tmp);
  }
  
}


/*
 * spawn  - spawn a subprocess and direct output to a file
 *
 * INPUT:  sad     - spawn argument descriptor
 *         output  - output file, may be NULL
 *         cmask   - protection mask
 *
 * RETURN: 0 on success, -1 on failure
 *
 */
int spawn(char **sad, const char *output, mode_t cmask) {
  int status,		/* fork status */
      fd;		/* output file descriptor */
  pid_t pid;		/* process id */
  
#ifdef DEBUG  
  extern int verbose;

  if (verbose) {
    int i;
    for (i=0; sad[i]; i++) {
      printf("sad[%d]=\"%s\"\n",i,sad[i]);
    }
  }
#endif
  
  /* spawn subprocess */
  pid=fork();

  /* failed? */
  if (pid<0) {
    message("",'E',"cannot fork subprocess");
    return(-1);
  }

  /* is this the subprocess? */
  if (pid==0) {
   
    /* redirect stdout? */
    if (output) {
     
      /* close stdout */
      close(1);

      /* open output file as stdout */
      fd=creat(output,0666&~cmask);
      if (fd!=1) {
        errno=0;
	message("",'E',"file descriptor mismatch");
	cleanup();
	exit(1);
      }

    }
    
    /* execute program */
    execvp(sad[0],sad);

    /* oops - failed */
    message("",'F',"execvp() failed!");
    cleanup();
    exit(2);
  }

  /* wait for termination of subprocess */
#ifdef NEXT
  wait(&status);
#else
  waitpid(pid,&status,0);
#endif

  /* error in subprocess? */
  if (status) return(-1);

  return(0);
}

/*
 * vsystem - system() with verbose output
 * 
 * INPUT: cmd	- shell command string
 * 
 * RETURN: system() return code
 */
int vsystem(const char *cmd) {
  char tmp[MAXLEN];
  extern int verbose;
  extern char *prg;
  
  if (verbose) {
    snprintf(MAXS(tmp),"shell-call: %s\n",cmd);
    message(prg,'I',tmp);
  }
  return(system(cmd));
}


/*
 * vpopen - popen() with verbose output
 * 
 * INPUT: cmd	- shell command string
 *        type	- popen type string (r or w)
 * 
 * RETURN: popen() FILE
 */
FILE* vpopen(const char *cmd, const char *type) {
  char tmp[MAXLEN];
  extern int verbose;
  extern char *prg;
  
  if (verbose) {
    *tmp = 0;
    switch (*type) {
      case 'r': snprintf(MAXS(tmp),"shell-call: %s|",cmd); break;
      case 'w': snprintf(MAXS(tmp),"shell-call: |%s",cmd); break;
    }
    message(prg,'I',tmp);
  }
  return(popen(cmd,type));
}


/*
 * shell_quote - quote a string so it ist shell escape safe
 * 
 * INPUT: string  - string to quote
 * 
 * RETURN: quoted string
 * 
 * REMARK: this function is not rentrant safe!
 */
char* shell_quote(const char *string) {
  static char quoted[MAXLEN];
  const char *special=" [](){}<>?*~#$&|;'\n\t\v\b\r\f\a\"\\";
  const char *sp;
  char *qp;
  
  qp=quoted;
  
  for (sp=string; *sp; sp++) {
    if (strchr(special,*sp)) {
      if (qp-quoted+2 >= MAXLEN) break;
      *qp='\\';
      qp++;
    }
    if (qp-quoted+1 >= MAXLEN) break;
    *qp=*sp;
    qp++;
  }
  qp++;
  *qp=0;
  return(quoted);
}


/*
int main(int argc, char *argv[]) {
  char *filepath;
  
  filepath=whereis(argv[1]);
  
  if (filepath) {
    printf("%s\n",filepath);
    exit(0);
  } 
  
  exit(1);
  
}
*/
