
/*
 * filetype.c
 *
 * Copyright (C) 1993 Alain Knaff
 */

#define _LARGEFILE64_SOURCE
#define _GNU_SOURCE

#include "sysincludes.h"
#include "stat.h"

static int is_initialised=0;
static FilenameActions *filenameActions;
static char *default_uncompressor[]= { UNCOMPR, 0 };

#ifndef SECURITY
static char *custom_uncompressor[]= { 0, 0 };
#endif

char *zlib_ext;
int zlib_extlen;

int zlib_mode;
char *zlib_tmp;

char **zlib_uncompressor;

#define ENV_PREFIX "LD_ZLIB_"

extern char **environ;

/**
 * Remove LD_PRELOAD=uncompress.o from environment in case CM_DISAB_CHILDREN
 * is set
 * We cannot use putenv or setenv here, because they work on a copy of the
 * environment, which will be overwritten between the time this _init is
 * called, and main is called
 */
static void remove_ld_preload() {
  if(zlib_mode & CM_DISAB_CHILD) {
    char preload_name[]="LD_PRELOAD=";
    char *uncompr = "/uncompress.o";      
    int i;
    for(i=0; environ[i]; i++) {
      if(!strncmp(environ[i], preload_name, sizeof(preload_name)-1)) {
	/* strip out all items contains "uncompress.o" from LD_PRELOAD var */
	char *rptr = environ[i] + sizeof(preload_name) - 1;
	char *lastbegin=rptr;
	char *wptr = rptr;
	int comp=1;
	int atbegin=1;
	lastbegin = wptr;
	while(1) {
	  if(*rptr == ':') {
	    if(uncompr[comp] == '\0') {
	      wptr = lastbegin;
	      if(atbegin) {
		rptr++;
		comp=1;
		continue;
	      }
	    }
	    comp = 1;
	    lastbegin = wptr;
	    atbegin=0;
	  } else if(*rptr == '\0') {
	    if(uncompr[comp] == '\0') {
	      wptr = lastbegin;
	    }
	    *wptr='\0';
	    break;	    
	  } else if(*rptr == uncompr[comp]) {
	    comp++;
	  } else {
	    comp=0;
	  }
	  *wptr++=*rptr++;	 
	}	
      }
    }
  }
}


static char *mode_table[] = {
  "DISABLE",
  "READDIR_COMPR",
  "VERBOSE",
  "UNLINK",
  "NOCONF"};

void zlib_initialise(void)
{
  int modemask;
  int olderrno;

#ifndef SECURITY
  unsigned int i;
  char env_buffer[1024];
  char *value;
#endif

  olderrno = errno;
  if ( !is_initialised ){
    CommandActions *cma;

#ifdef HAVE_PROC
    char static_cmdline[1025];
    int fd,n;
#endif
    char *cmdline;
    char *progname=0;

    zlib_mode = modemask = 0;

    zlib_ext = EXT;
    zlib_extlen = strlen(zlib_ext);
    zlib_uncompressor = default_uncompressor;
    zlib_tmp = TMP;

#ifndef SECURITY
    if (getuid() == geteuid() || getgid() == getegid()){
      zlib_tmp = getenv("LD_ZLIB_TMP");
      if ( zlib_tmp == 0 )
	zlib_tmp = TMP;
      
      zlib_ext= getenv("LD_ZLIB_EXT");
      if ( zlib_ext == 0 )
	zlib_ext = EXT;
      zlib_extlen = strlen(zlib_ext);
      if ( zlib_extlen > MAXEXTLEN ){
	fprintf(stderr,"extension too long, taking default\n");
	zlib_ext = EXT;
      }
      custom_uncompressor[0] = getenv("LD_ZLIB_UNCOMPRESSOR");
      if ( custom_uncompressor[0] ){
	zlib_uncompressor = custom_uncompressor;
      } else
	zlib_uncompressor = default_uncompressor;
      
      strcpy(env_buffer, ENV_PREFIX);
      for ( i=0; i< ( sizeof(mode_table) / sizeof(char *) ); i++){
	strcpy( env_buffer+(sizeof( ENV_PREFIX ) - 1 ), mode_table[i] );
	if ( (value=getenv ( env_buffer )) ){
	  if ( ( strcmp( value, "on" ) == 0 ) || ( strcmp( value, "1" ) == 0 )){
	    zlib_mode |= ( 1 << ( i + 1 ));
	  }else 
	    if ( ( strcmp( value, "off" ) ) && ( strcmp( value, "0" ) ))
	      continue;
	  modemask |= ( 1 << ( i + 1 ));	  
	}
      }
      if ( zlib_mode & CM_DISAB){
	is_initialised = 2;
	errno = olderrno;
	return;
      }
    }
#endif

#ifdef HAVE_PROC
    /* get command line */    
    cmdline=static_cmdline;
    strcpy(cmdline, "unknown");
    fd=syscall(SYS_open,"/proc/self/cmdline", O_RDONLY);
    if ( fd > 0 ){
      cmdline[1024]='\0';
      n = read( fd, cmdline,1024 );
      if ( n < 1 )
	cmdline[0] = '\0';
      else
	cmdline[n] = '\0';
      close(fd);
    } else {
#endif
    /* where does the command line live on a sun ? */
    /* horrible undocumented kludge... This might even work on a proc-less 
     * linux*/
      do {
	int zerosfound;
	
	if ( environ == 0 ){
	  cmdline="";
	  break;
	}
	cmdline = *environ;
	
	if ( cmdline == 0 ){
	  cmdline="";
	  break;
	}
	
	zerosfound = 0;
	while(zerosfound < 2 ){
	  cmdline--;
	  if ( *cmdline == 0 )
	    zerosfound++;
	  else
	    zerosfound = 0;
	}
	cmdline +=2;
      } while(0);
    }

    progname = strrchr(cmdline, '/' );

    if ( !progname )
      progname = cmdline;
    else
      progname++;

    if ( zlib_mode & CM_VERBOSE)
      fprintf(stderr,"progname = %s\n",progname);

    is_initialised = 1;

    /* get user configuration */
#ifndef NO_RT_CONF
    if ( ! ( zlib_mode & CM_NORTCONF ) )
      zlib_getuserconf(progname, &filenameActions, &zlib_mode, &modemask);
#endif
    if ( modemask != CM_ALL_MODES ){
      /* if we couldn't get the user configuration, use compiled in defaults */
      cma = zlib_commandActions;
      while ( cma->name && strcmp(cma->name, progname ) )
	cma++;

      if ( ( modemask & CM_HAVE_FA ) == 0 )
	filenameActions = cma->actions;

      zlib_mode |= ( cma->cm_type & ~modemask);
    }


    remove_ld_preload();
    is_initialised = 2;
    /* initialisation completely done */
  } 
  errno = olderrno;
}

#ifdef __ELF__

#ifdef RTLD_NEXT
int (*zlib_real_access)(const char *, int);
int (*zlib_real_chmod) (const char *, mode_t);
int (*zlib_real_chown) (const char *, uid_t, gid_t);
int (*zlib_real_getdents) (unsigned int, struct dirent *, unsigned int);
#ifdef SYS_getdents64
int (*zlib_real_getdents64) (unsigned int, struct dirent64 *, unsigned int);
#endif
int (*zlib_real_link) (const char *, const char *);
int (*zlib_real_open) (const char *, int, mode_t);
#ifdef linux
FILE *(*zlib_real_fopen) (const char *, const char *);
FILE *(*zlib_real_fopen64) (const char *, const char *);
#endif
struct dirent *(*zlib_real_readdir) (DIR *);
struct dirent64 *(*zlib_real_readdir64) (DIR *);
int (*zlib_real_readlink) (const char *, char *, size_t);
int (*zlib_real_rename) (const char *, const char *);


# ifdef HAVE___XSTAT
int (*zlib_real_xstat) (int vers, const char *, struct stat *);
int (*zlib_real_lxstat) (int vers, const char *, struct stat *);
# else
int (*zlib_real_stat) (const char *, struct stat *);
int (*zlib_real_lstat) (const char *, struct stat *);
# endif

# ifdef HAVE___XSTAT64
int (*zlib_real_xstat64) (int vers, const char *, struct stat64 *);
int (*zlib_real_lxstat64) (int vers, const char *, struct stat64 *);
# elif defined HAVE_STAT64
int (*zlib_real_stat64) (const char *, struct stat64 *);
int (*zlib_real_lstat64) (const char *, struct stat64 *);
# endif


int (*zlib_real_symlink) (const char *, const char *);
int (*zlib_real_unlink) (const char *);
int (*zlib_real_utime) (const char *, struct utimbuf *);
int (*zlib_real_utimes) (const char *, struct timeval *);

#ifdef HAVE_GETXATTR
ssize_t (*zlib_real_getxattr) (const char *, const char *, void *, size_t);
ssize_t (*zlib_real_lgetxattr) (const char *, const char *, void *, size_t);
int (*zlib_real_setxattr) (const char *, const char *, const void *, 
			   size_t, int);
int (*zlib_real_lsetxattr) (const char *, const char *, const void *, 
			    size_t, int);
ssize_t (*zlib_real_listxattr) (const char *, char *, size_t);
ssize_t (*zlib_real_llistxattr) (const char *, char *, size_t);
int (*zlib_real_removexattr) (const char *, const char *);
int (*zlib_real_lremovexattr) (const char *, const char *);
#endif

#endif

static int _is_rtld_initialized = 0;


int _zlibc_init(void)
{
    if(_is_rtld_initialized) {
	return 0;
    }
    _is_rtld_initialized=1;
#ifdef RTLD_NEXT
  zlib_real_access = dlsym(RTLD_NEXT, "access");
  zlib_real_chmod = dlsym(RTLD_NEXT, "chmod");
  zlib_real_chown = dlsym(RTLD_NEXT, "chown");
  zlib_real_getdents = dlsym(RTLD_NEXT, "_getdents");
#ifdef SYS_getdents64
  zlib_real_getdents64 = dlsym(RTLD_NEXT, "_getdents64");
#endif
  zlib_real_link = dlsym(RTLD_NEXT, "link");
# ifdef HAVE___LIBC_OPEN
  zlib_real_open = dlsym(RTLD_NEXT, "__libc_open");
# else
  zlib_real_open = dlsym(RTLD_NEXT, "_open");
# endif
  if(zlib_real_open == NULL)
    zlib_real_open = dlsym(RTLD_NEXT, "open");
#ifdef linux
  zlib_real_fopen = dlsym(RTLD_NEXT, "fopen");
  zlib_real_fopen64 = dlsym(RTLD_NEXT, "fopen64");
#endif
  zlib_real_readdir = dlsym(RTLD_NEXT, "readdir");
  zlib_real_readdir64 = dlsym(RTLD_NEXT, "readdir64");
  zlib_real_readlink = dlsym(RTLD_NEXT, "readlink");
  zlib_real_rename = dlsym(RTLD_NEXT, "rename");
  zlib_real_symlink = dlsym(RTLD_NEXT, "symlink");
  zlib_real_unlink = dlsym(RTLD_NEXT, "unlink");
  zlib_real_utime = dlsym(RTLD_NEXT, "utime");
  zlib_real_utimes = dlsym(RTLD_NEXT, "utimes");

# ifdef HAVE___XSTAT
  zlib_real_xstat = dlsym(RTLD_NEXT, "__xstat");
  zlib_real_lxstat = dlsym(RTLD_NEXT, "__lxstat");
# else
  zlib_real_stat = dlsym(RTLD_NEXT, "stat");
  zlib_real_lstat = dlsym(RTLD_NEXT, "lstat");
# endif


# ifdef HAVE___XSTAT64
  zlib_real_xstat64 = dlsym(RTLD_NEXT, "__xstat64");
  zlib_real_lxstat64 = dlsym(RTLD_NEXT, "__lxstat64");
# elif defined HAVE_STAT64
  zlib_real_stat64 = dlsym(RTLD_NEXT, "stat64");
  zlib_real_lstat64 = dlsym(RTLD_NEXT, "lstat64");
# endif

#endif

#ifdef HAVE_GETXATTR
  zlib_real_getxattr = dlsym(RTLD_NEXT, "getxattr");
  zlib_real_lgetxattr = dlsym(RTLD_NEXT, "lgetxattr");
  zlib_real_setxattr = dlsym(RTLD_NEXT, "setxattr");
  zlib_real_lsetxattr = dlsym(RTLD_NEXT, "lsetxattr");
  zlib_real_listxattr = dlsym(RTLD_NEXT, "listxattr");
  zlib_real_llistxattr = dlsym(RTLD_NEXT, "llistxattr");
  zlib_real_removexattr = dlsym(RTLD_NEXT, "removexattr");
  zlib_real_lremovexattr = dlsym(RTLD_NEXT, "lremovexattr");
#endif

  zlib_initialise();
  return 0;
}

#endif

static int masks[]= { 
	PM_READ_MASK,
	PM_CREATE_MASK,
	PM_APPEND_MASK,
	PM_UNCOMPR_MASK,
	PM_SIZE_COMPR_MASK
};
#define	NUMBER(x)	(sizeof(x) / sizeof(*(x)))


static void quick_stat(__const char *name, dev_t *dev, ino_t *ino)
{
  int olderrno;
  int st;
  struct stat buf;

  olderrno = errno;
  st = ___zlibc_nativestat(name, &buf);
  errno = olderrno;
  if ( st < 0 ) {
    /* file not found */
    *ino = 0;
    *dev = 0;
  } else {
    *dev = buf.st_dev;
    *ino = buf.st_ino;
  }
}

static void initialize_fa(FilenameActions *fa)
{
  if(fa->is_initialized)
    return;
  fa->is_initialized = 1;
  quick_stat(fa->name, &fa->dev, &fa->ino);
}

static int check_subdir(__const char *name, int dirlength,
			int fd, FilenameActions *fa, int subdir)
{
  char dirname[MAXPATHLEN+1];
  struct stat buf;
  dev_t last_dev=0;
  ino_t last_ino=0;
  int cwd;
  int cfd;
  int closefd=0; /* fd must be closed on exit */
  int r;

  initialize_fa(fa);
  if(!fa->ino)
    return 0;

  if(fd == -1){
    if(dirlength - 1 > MAXPATHLEN)
      return 0;
    if(!dirlength)
      strcpy(dirname,".");
    else
      strncpy(dirname,name, dirlength-1);
    closefd = fd = zlib_real_open(dirname, O_RDONLY, 0);
    if(fd < 0)
      return 0;
  }

  cwd = -1;
  cfd = fd;
  while(1) {
      fstat(cfd, &buf);
    if(cwd >= 0)
      close(cfd);
    if(buf.st_dev == fa->dev &&
       buf.st_ino == fa->ino) {
      r = 1;
      break;
    }
    if(!subdir) {
      r = 0;
      break;
    }
    if(cwd >= 0 &&
       buf.st_dev == last_dev &&
       buf.st_ino == last_ino) {      
      /* root reached */
      r = 0;
      break;
    }
    last_dev = buf.st_dev;
    last_ino = buf.st_ino;
#ifdef HAVE_FCHDIR
    if(cwd < 0) {
      /* remember old directory */
      cwd = zlib_real_open(".", O_RDONLY, 0);
      if(cwd < 0) {
	r = 0;
	break;
      }
      fchdir(fd);  /* go to the new directory */
    }
    chdir(".."); /* move up one level */
    cfd = zlib_real_open(".", O_RDONLY, 0);
    if(cfd < 0) {
      r = 0;
      break;
    }
#else
    if(fd != -1) {
      /* unknown filename */
      r = 0;
      break;
    }
    if(strlen(dirname) > MAXPATHLEN - 3) {
      r = 0; /* filename too long */
      break;
    }
    strcat(dirname, "/..");
    cfd = zlib_real_open(dirname, O_RDONLY, 0);
    if(cfd < 0) {
      r = 0;
      break;
    }
#endif
  }

#ifdef HAVE_FCHDIR
  if(cwd != -1) {
    /* revert back to real current directory */
    fchdir(cwd);
    close(cwd);
  }
#endif
  if(closefd)
    close(fd);
  return r;
}

int zlib_getfiletype(__const char *name, int fd)
{
  __const char *basename;

  FilenameActions *fa;
  int length;
  int dirlength;
  int basenamelength;
  int pipe_mode;
  unsigned int i;
  int match;
  int dostop;
  int dev;
  struct stat buf;
  int olderrno;
  int st;

  /* preprocessing */
  dev=-1;
  basename = strrchr( name, '/' );
  if ( !basename )
    basename = name;
  else
    basename++;
  
  dirlength = basename - name;
  basenamelength = strlen(basename);
  length = dirlength + basenamelength;

  zlib_initialise();

  if ( is_initialised == 1 )
    /* transition period, loading user supplied defaults */
    return PM_SHOW_PIPE;

  fa = filenameActions;
  pipe_mode=0;
  while ( 1 ){
    match=0;
    switch( fa->fa_type ){
      /* before calling strncmp, the length are compared in order to speed up
       * the search. In most cases, the length doesn't match, this way we don't
       * need a costly call to strncmp */
    case FA_ALL:
    case FA_ALL2:
      match=1;
      break;
    case FA_DIR:
      if(fd == -1 && name[0] == '/')
	match= fa->namelength == dirlength &&
	  !strncmp(fa->name, name, dirlength);
      else
	match = check_subdir(name, dirlength, fd, fa, 0);
      break;
    case FA_SUBDIR:
      if(fd == -1 && name[0] == '/')	
	match= fa->namelength<=dirlength &&
	  !strncmp(fa->name,name,fa->namelength);
      else
	match = check_subdir(name, dirlength, fd, fa, 1);
      break;
    case FA_BASENAME:
      match= fa->namelength == basenamelength && !strcmp(fa->name, basename);
      break;
    case FA_FILENAME:
      match= fa->namelength == length && !strcmp(fa->name, name);
      break;
    case FA_SUFFIX:
      match= fa->namelength <= basenamelength &&
	      !strcmp(fa->name, basename + (basenamelength - fa->namelength));
      break;
    case FA_FS:
      if ( dev == -1 ){
	olderrno = errno;

	if ( fd != -1 )
	  st = fstat(fd,&buf);
	else {
	  char namez[MAXPATHLEN + MAXEXTLEN + 1];
	  strncpy(namez, name, MAXPATHLEN);
	  strncat(namez, zlib_ext, MAXPATHLEN);
	  st = ___zlibc_nativestat(namez, &buf );
	}
	errno = olderrno;
	if ( st < 0 )
	  dev = 0;
	else
	  dev = buf.st_dev;
      }
      match = buf.st_dev == fa->namelength;
      break;
    default:
      fprintf(stderr,"Error in filenameActions %x in %d\n", fa->fa_type, 
	      (int) getpid());
      sleep(3);
      return 0;
      break;
    }
    if (match){
      dostop=1;
      for (i=0; i< NUMBER(masks); i++){
	if (!(pipe_mode & masks[i]))
	  pipe_mode |= fa->pipe_mode & masks[i];
	if (!(pipe_mode & masks[i]))
	  dostop=0;
      }
      if ( dostop || fa->fa_type == FA_ALL){
#ifdef DEBUG
	fprintf(stderr,"pm=%x\n", pipe_mode);
#endif
	return pipe_mode;
      }
    }
    fa++;
  }
}


