#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>    /* <afs/venus.h> needs this */
#include <afs/param.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <afs/vice.h>
#include <afs/venus.h>
#include <sys/param.h>

#ifdef ultrix
#include <sys/mount.h>
#include <sys/fs_types.h>
#endif

#include "q.h"

#ifndef S_ISDIR
#define S_ISDIR(mode)  (((mode) & S_IFMT) == S_IFDIR)
#endif

/* Q_expand_file_name() returns the full path to the partial
 * or relative filename path, or NULL on error.
 *
 * Note: this routine will return NULL if any of the directories
 * leading up to the file don't exist or can't be read.  However,
 * the trailing component does not have to exist.  For example,
 * it will expand /mit/ckclark/foo to
 *
 *        /afs/athena.mit.edu/user/c/k/ckclark/foo
 *
 * even if there is no file 'foo' in my homedir.  (There usually is,
 * but that's beside the point.)
 *
 * However, it will return NULL and errno 2 in err_code (No such file or
 * directory) on /mit/ckclark/foo/bar, if foo doesn't exist.
 *
 */

char *
Q_expand_file_name (path, err_code)
     char *path;
     long *err_code;
{
  char *full_path, *save_pwd;
  char *tmp;
  struct stat stat_buf;
  int is_dir = 0;
  
  full_path = (char *) Q_malloc (MAXPATHLEN);
  save_pwd = (char *) Q_malloc (MAXPATHLEN);
  
  if (stat (path, &stat_buf) == 0 && S_ISDIR (stat_buf.st_mode))
    {
      is_dir++;
      full_path = path;
    }
  
  tmp = (char *) rindex (path, '/');
  
  if (tmp == NULL && !is_dir)	/* file in current directory */
    {
      if ((void*)getwd (full_path) == NULL)
	goto fail;
      strcat (full_path, "/");
      strcat (full_path, path);
    }
  else
    {
      if ((void*)getwd (save_pwd) == NULL)
	goto fail;

      if (!is_dir)
	strncpy (full_path, path, ++tmp - path);

      if (chdir (full_path) < 0)
	goto fail;
      if ((void*)getwd (full_path) == NULL)
	goto fail;

      if (!is_dir)
	{
	  if (strcmp (full_path, "/") != 0)
	    strcat (full_path, "/");
	  strcat (full_path, tmp);
	}
      (void) chdir (save_pwd);	/* I don't care if this fails */
      Q_free (save_pwd);
    }

  return full_path;

 fail:
  Q_free (full_path);
  Q_free (save_pwd);
  *err_code = errno;
  return NULL;
}

/*
 * Returns one of ON_AFS, ON_NFS, or ON_UFS (defined in q.h), indicating
 * the type the type of file system the file `path' is on.  Symbolic
 * links are followed.  Returns -1 on error.
 */

int
Q_file_system_type (path, err_code)
     char *path;
     long *err_code;
{
  struct ViceIoctl spew;	/* You laugh at my choice of name,
			           but that's exactly what this is. */
  long ret_val;
  static char buf[AFSMAXSIZE];
  struct stat statbuf;
  char *newpath;
  char *dirpath;
  int fh, c;
  long code;
  extern char *dirname ();	/* this is in libfu.a (GNU fileutils lib.) */
  
  Q_init ();			/* initializes com_err table */
  
  /* First, see if file exists at all */
  
  if (lstat (path, &statbuf) < 0)
    {
      *err_code = errno;
      return -1;
    }
  
  if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
    {
      newpath = Q_expand_file_name (path, &code);
      if (newpath == NULL)
	{
	  *err_code = code;
	  return -1;
	}
    }
  else
    newpath = path;
  
  if (stat (newpath, &statbuf) < 0)
    {
      *err_code = errno;
      return -1;
    }
  
  /*
   * The operations for checking filesystems work on directories.
   * if this file is a directory, use that.  Otherwise,
   * use the directory it is in.
   */
  
  if (S_ISDIR (statbuf.st_mode))
    dirpath = newpath;
  else
    dirpath = dirname (newpath);
  
  /*
   * Check AFS first, since this is the type of filesystem most
   * people are going to be using when this is package is available.
   */
  
  spew.in_size = 0;
  spew.out_size = AFSMAXSIZE;
  spew.out = buf;
  
  /* pioctl() is your friend.  NOT! */
  
  ret_val = pioctl (dirpath, VIOC_FILE_CELL_NAME, &buf, 1);
  
  if (ret_val == 0)
    return ON_AFS;
  
  if (errno != EINVAL && errno != ENOENT)
    {
      *err_code = errno;
      return -1;
    }
  
  /*
   * Here's where it starts to get seriously non-portable.  This
   * code is taken from the Athena login sources.  It will
   * probably have to be changed for each new platform.
   */
  
#ifdef _AIX
  {
    struct stat stbuf;
    
    if (statx (dirpath, &stbuf, 0, STX_NORMAL))
      return ON_NFS;
    return ((stbuf.st_flag & FS_REMOTE) ? ON_NFS : ON_UFS);
  }
#endif
  
#ifdef ultrix
  {
    struct fs_data sbuf;
    
    if (statfs (dirpath, &sbuf) < 0)
      return ON_NFS;
    
    switch (sbuf.fd_req.fstype)
      {
      case GT_ULTRIX:
      case GT_CDFS:
	return ON_UFS;
      }
    return ON_NFS;
  }
#endif
  
#if (defined (vax) || defined (ibm032) || defined (sun))
#if defined (vax) || defined (ibm032)
#define NFS_MAJOR 0xff
#endif
#ifdef sun
#define NFS_MAJOR 130
#endif
  {
    struct stat stbuf;
    
    if (stat (dirpath, &stbuf))
      return ON_NFS;
    
    if (major (stbuf.st_dev) == NFS_MAJOR)
      return ON_NFS;
    
    return ON_UFS;
  }
#endif
}
