/* gds___server.c
                              COPYRIGHT (c) 1995
                        Kapteyn Astronomical Institute
         University of Groningen  -  9700 AV Groningen, The Netherlands
*/

#define IDENTFILE ".gds_ident"
#define DESCR     ".descr"
#define LNBLEN    256

#include "stddef.h"

#ifdef	__linux__
#define	__LINUX_UIO_H
#endif
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/stat.h>


#if defined(__linux__)
#include "sockio.h"
#endif

#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "xscanf.h"
#include "netaddr.h"
#include "gdscom.h"
#include "userfio.h"
#include "gds___server.h"

typedef struct _server {   
   struct _server *next;    /* link to next block */
   char      *dir;          /* directory name */
   char      *id;           /* server id */
   int        fd;           /* communication socket */
   fint       result;       /* last operation's result code */
   fint       nbytes;       /* number of bytes to be read or written */
} _server, *server;    /* ------------------------------ server status block */

#define New(type) ((type *)calloc(1,sizeof(type)))
#define NNew(n,type) ((type *)calloc(n,sizeof(type)))
#define Delete(x)  {free(x); x=NULL;}

static char *RealName(char *name);
static char *StrDup(char*);
static int ServerConnect(server srv);
static char *gds___dirid(char *dirname);
static server srvlist=NULL;
static int srverr;


/* ========================================================================== */
/*                             gds___server                                   */
/* -------------------------------------------------------------------------- */
/*
#>gds___server.dc3
Function:     gds___server

Purpose:      Obtain a GDS server status block pointer, given a set name.

Category:     GDS, SYSTEM

File:         gds___server.c

Author:       J.P. Terlouw

Use:          #include "gds___server.h"
              gdsserver gds___server(char *setname);

Description:  gds___server() obtains a pointer to a structure associated with
              a gds server process. This pointer can subsequently be used in
              calls to gds___srvreq(), gds___srvrcv(), and gds___srvsnd().
              In case of failure NULL is returned. In this case gds___srverr()
              can be called to obtain the error code.

Related docs: gds___srvreq.dc3, gds___srvsnd.dc3, gds___srvrcv.dc3, 
              gds___srvcls.dc3, gds___srvnam.dc3, gds___srverr.dc3.

Updates:      Mar 28, 1995: JPT, Document created.
              May 10, 1995: JPT, Reduced number of I/O's in gds___srvreq.
              Sep  1, 1995: JPT, Fixed inaccessible directory bug.
              Sep 28, 1995: JPT, Translate symbolic links.
              Sep 29, 1995: JPT, Fixed absolute symbolic link bug.
              Sep  2, 1996: JPT, Small code change by KGB.
              Feb 16, 1998: JPT, Forcefully close all sets when fd table full.
              Nov 19, 1998: JPT, Retry in case of bad sockets file.
              Sep 19, 2000: JPT, Check GDS server startup failure.
#<
*/

extern gdsserver gds___server(char *name)
{
   server current;
   char   *dir, *srvid, *dirid;
   int    i;
    
   dir = RealName(name);
   for (i=strlen(dir)-1; i>0; i--) {
      if (dir[i-1]=='/') {
         dir[i] = '\0';
         break;
      }
   }

/*
 * Check whether directory name is already served.
 */
   for (current=srvlist; current; current=current->next) {
      if (!strcmp(dir,current->dir)) {
         Delete(dir);
         return (gdsserver)current;
      }
   }
   
/*
 * Check whether directory is served under a different name.
 */
   dirid = gds___dirid(dir);
   if (!dirid) {
      srverr = GDS_BADDIR;
      return (gdsserver)NULL;
   }
   srvid = StrDup(dirid);
   for (current=srvlist; current; current=current->next) {
      if (!strcmp(srvid,current->id)) {
         Delete(current->dir);                      /* delete oldest name */
         current->dir = dir;                        /* keep newest name   */
         Delete(srvid);
         return (gdsserver)current;
      }
   }
   
/*
 * No appropriate server known: find server.
 */
   current      = New(_server);
   current->dir = dir;
   current->id  = srvid;
   if (!ServerConnect(current)) {
      current->next = srvlist;
      srvlist       = current;
   } else {
      Delete(current->dir);
      Delete(current->id);
      Delete(current);
      srverr = GDS_CONNFAIL;
   }
   return (gdsserver)current;
}

/* ========================================================================== */
/*                                 gds___srvreq                               */
/* -------------------------------------------------------------------------- */
/*
#>gds___srvreq.dc3
Function:     gds___srvreq

Purpose:      Issue a request to a GDS server.

Category:     GDS, SYSTEM

File:         gds___server.c

Author:       J.P. Terlouw

Use:          #include "gds___server.h"
              int gds___srvreq(gdsserver id,
                               void *request,
                               int size,
                               int *nbytes)
   
              id         server 'object', obtained by calling gds___server().
              request    request to server. Requests are defined in gdscom.h.
              size       size of request (bytes).
              nbytes     output argument. If not NULL, it will receive the
                         number of bytes to be read or written as a result
                         of this request.
               
              return value: zero on success; non-zero in case of errors.
              
              A set name in a request must be specified relative to the
              directory in which the set resides.

Related docs: gds___server.dc3, gds___srvsnd.dc3, gds___srvrcv.dc3, 
              gds___srvcls.dc3, gds___srvnam.dc3.

Updates:      Mar 28, 1995: JPT, Document created.
              May 10, 1995: JPT, Reduced number of I/O's.
#<
*/
extern int gds___srvreq(gdsserver id, void *request, int size, int *nbytes)
{
   server current=(server)id;
   fint rsize=(fint)size;
   struct {
      fint       size;
      _uRequest  request;
   } reqmsg;
   
   _gdsReply replymsg;

   reqmsg.size = rsize;
   memcpy(&reqmsg.request, request, size);
   write(current->fd, &reqmsg, size+sizeof(reqmsg.size));

   read(current->fd, &replymsg, sizeof(replymsg));
   current->result = replymsg.result;
   current->nbytes = replymsg.length;
   if (nbytes) *nbytes = current->nbytes;
   return current->result;
}

/* ========================================================================== */
/*                                 gds___srvsnd                               */
/* -------------------------------------------------------------------------- */
/*
#>gds___srvsnd.dc3
Function:     gds___srvsnd

Purpose:      send data associated with the current request to a GDS server.

Category:     GDS, SYSTEM

File:         gds___server.c

Author:       J.P. Terlouw

Use:          #include "gds___server.h" 
              int gds___srvsnd( gdsserver id, void* data, int nbytes)
              
              id         server 'object', obtained by calling gds___server().
              data       data to be sent
              nbytes     number of bytes to be sent.
              
              return value:
              success:   number of bytes still to be transferred;
              error:     -1

Related docs: gds___server.dc3 gds_srvreq.dc3, gds___srvrcv.dc3,
              gds___srvcls.dc3, gds___srvnam.dc3.

Updates:      Mar 28, 1995: JPT, Document created.
#<
*/
extern int gds___srvsnd(gdsserver id, void* data, int nbytes)
{
   server current=(server)id;
   int    result;
   
   if ((nbytes>=0) && (nbytes <= current->nbytes)) {
      if (nbytes) {
#if defined(__linux__)
         (void)sock_write(current->fd,data,nbytes);
#else
         (void)write(current->fd,data,nbytes);
#endif
      }
      current->nbytes -= nbytes;
      result = current->nbytes;
   } else result = -1;
   return result;
}
   
/* ========================================================================== */
/*                                 gds___srvrcv                               */
/* -------------------------------------------------------------------------- */
/*
#>gds___srvrcv.dc3
Function:     gds___srvrcv

Purpose:      receive data associated with the current request from GDS server.

Category:     GDS, SYSTEM

File:         gds___server.c

Author:       J.P. Terlouw

Use:          #include "gds___server.h" 
              int gds___srvrcv( gdsserver id, void* data, int nbytes)
              
              id         server 'object', obtained by calling gds___server().
              data       data to be received
              nbytes     number of bytes to be received.
              
              return value:
              success:   number of bytes still to be transferred;
              error:     -1

Related docs: gds___server.dc3 gds_srvreq.dc3, gds___srvsnd.dc3,
              gds___srvcls.dc3, gds___srvnam.dc3.

Updates:      Mar 28, 1995: JPT, Document created.
#<
*/
extern int gds___srvrcv(gdsserver id, void* vdata, int nbytes)
{
   server current=(server)id;
   char   *data=(char*)vdata;
   int    result;
   int    nread=0;
   
   if (nbytes>=0) {
      while (nbytes>0) {
         nread = read(current->fd,data,nbytes);
         if (nread>=0) {
            nbytes -= nread;
            current->nbytes -= nread;
            data   += nread;
            if (current->nbytes <= 0) break;
         } else {
            return -1;
         }
      }
      result = current->nbytes;
   } else result = -1;
   return result;
}

/* ========================================================================== */
/*                                 gds___srvcls                               */
/* -------------------------------------------------------------------------- */
/*
#>gds___srvcls.dc3
Function:     gds___srvcls

Purpose:      Close all GDS server connections

Category:     GDS, SYSTEM

File:         gds___server.c

Author:       J.P. Terlouw

Use:          #include "gds___server.h"
              void gds___srvreq( void )

Related docs: gds___server.dc3, gds___srvsnd.dc3, gds___srvrcv.dc3, 
              gds___srvreq.dc3, gds___srvnam.dc3.

Updates:      Mar 28, 1995: JPT, Document created.
#<
*/
extern void gds___srvcls(void)
{
   server current;
    
   while (current=srvlist) {
      srvlist = current->next;
      close(current->fd);
      Delete(current->dir);
      Delete(current->id);
      Delete(current);
   }
}

/* ========================================================================== */
/*                                 gds___srvnam                               */
/* -------------------------------------------------------------------------- */
/*
#>gds___srvnam.dc3
Function:     gds___srvnam

Purpose:      Obtain GDS server-relative set name, given the full set path.

Category:     GDS, SYSTEM

File:         gds___server.c

Author:       J.P. Terlouw

Use:          #include "gds___server.h"
              char *gds___srvnam(gdsserver id, char *name )

              id         server 'object', obtained by calling gds___server().
              name       full-path set name.
              
              return value:
              pointer to set name as expected by the GDS server

Related docs: gds___server.dc3, gds___srvsnd.dc3, gds___srvrcv.dc3, 
              gds___srvreq.dc3, gds___srvcls.dc3.

Updates:      Mar 28, 1995: JPT, Document created.
              Sep 28, 1995: JPT, Translate symbolic links
              Sep 29, 1995: JPT, Fixed absolute symbolic link bug.
#<
*/
extern char *gds___srvnam(gdsserver id, char *name)
{
   static char result[GDS_NAMLEN+1];
   char        *cp;
   char        *fullname;
    
   fullname = RealName(name);
   for(cp=fullname+strlen(fullname)-1; cp>fullname; cp--) {
      if (*(cp-1)=='/') break;
   }
   strcpy(result,cp);
   Delete(fullname);
   return result;
}

/* ========================================================================== */
/*                                 gds___srverr                               */
/* -------------------------------------------------------------------------- */
/*
#>gds___srverr.dc3
Function:     gds___srverr

Purpose:      Obtain GDS error code associated with server connect failure

Category:     GDS, SYSTEM

File:         gds___server.c

Author:       J.P. Terlouw

Use:          #include "gds___server.h"
              int gds___srverr(void)

              return value:
              GDS error code; only valid after a failed call to gds___server().

Related docs: gds___server.dc3

Updates:      Sep  1, 1995: JPT, Document created.
#<
 */
extern int gds___srverr(void)
{
   return srverr;
}
/* -------------------------------------------------------------------------- */
/*                                 ServerConnect                              */
/* -------------------------------------------------------------------------- */
static int ServerConnect(server current)
{

   int    result, nbytes, status;
   int    fd, fd2, pwd, nf;
   int    started=0;
   FILE   *f;
   char   filename[SNLEN+1];
   char   inet_name[SNLEN+1];
   char   unix_name[SNLEN+1];
   char   host[SNLEN+1];
   
   union {
      fint l;
      char b[sizeof(fint)];
   } u;
   
   _gdsConnect request;
    
   fd  = dup(0);
   fd2 = dup(0);
   if (fd2<0) {
      fint one=1;
      error_c(&one,tofchar("gds___server: temporary close of all sets"));
      gds_closeall_c(); /* try to obtain sufficient file descriptors */
   }
   close(fd);
   close(fd2);

   sprintf(filename,"%s/.gds_sockets%s",getenv("HOME"),current->id);
   if (!(f = fopen(filename,"r"))) {
      /* no sockets file: start server */
      int pid=fork();
      if (!pid) {
                                  /* ---- child (server) process branch ---- */
         char execname[100];
         if (chdir(current->dir)) {
            printf("chdir failed\n");
            exit(1);
         }
         strcpy(execname,getenv("gip_exe"));
         strcat(execname,"/gdsserver");
         if (execl(execname,"gdsserver",current->id,NULL)) exit(1);
      } else {
                                 /* ---- parent (client) process branch ---- */
         waitpid(pid, &status, 0);             /* wait until server is ready */
         if (status) errorf(4, "GDS server failure - cannot write in $HOME");
         started = 1;
         if (!(f = fopen(filename,"r"))) return -1;  /* sockets file problem */
      }
   }
   nf = xscanf( f, "%*s %*s %*s", 
               SNLEN, host, SNLEN, unix_name, SNLEN, inet_name);
   fclose(f);
   if (nf<2) {
      remove(filename);                                  /* bad sockets file */
      return ServerConnect(current);
   }
   
   if (!strcmp(host,netaddr())) {
                                     /* server on this host: use UNIX socket */
      struct sockaddr_un  uxaddr;              /* unix socket address struct */
       
      fd = socket( AF_UNIX, SOCK_STREAM, 0 );               /* create socket */
      if (fd<0) return 0;
      uxaddr.sun_family = AF_UNIX;
      strcpy( uxaddr.sun_path, unix_name );
      if (connect(fd,(struct sockaddr *)&uxaddr, sizeof(uxaddr)) < 0 ) {
      /* failed to connect - if existing sockets file: try again, else fail */
         close(fd);
         remove(filename);
         return started ? -1 : ServerConnect(current);
      }
   } else {
                                    /* 'foreign' server: use Internet socket */
      short               port;              /* inet port number */
      struct hostent      *hp;               /* host entry struct */
      struct hostent      *gethostbyname( ); /* get host info */
      struct sockaddr_in  inaddr;            /* inet socket address struct */
          
      fd = socket( AF_INET, SOCK_STREAM, 0);              /* create socket */
      if (fd<0) return 0;
      port = atoi(inet_name);
      inaddr.sin_addr.s_addr = inet_addr(host);
      inaddr.sin_family = AF_INET;
      inaddr.sin_port   = htons(port);
      if (connect(fd,(struct sockaddr *)&inaddr, sizeof(inaddr)) < 0 ) {
      /* failed to connect - if existing sockets file: try again, else fail  */
         close(fd);
         remove(filename);
         return started ? -1 : ServerConnect(current);
      }
   }
   /*
    *  Try to get write permission, or else read permission.
    */
   sprintf(filename,"%s/.gds_write",getenv("HOME"));
   f = fopen(filename,"r");
   if (!f) {
      sprintf(filename,"%s/.gds_read",getenv("HOME"));
      f = fopen(filename,"r");
   }
   if (f) {
      fscanf(f, "%d", &pwd);
      fclose(f);
   }
   current->fd = fd;
   request.head.code  = GDS_CONNECT;
   u.b[0]             = OS_INTEGER_TYPE;
   u.b[1]             = OS_FLOATING_TYPE;
   request.head.id    = u.l;
   request.pwd        = pwd;
   request.version    = VERSION;
   request.subversion = SUBVERSION;
   result = gds___srvreq((gdsserver)current, (void*)&request,
                          sizeof(request), &nbytes);
   if (result) close(fd);
   return result;
}


/* -------------------------------------------------------------------------- */
/*                                 gds___dirid                                */
/* -------------------------------------------------------------------------- */
/*  gds___dirid() returns a unique identification string associated with
 *  the directory specified in the argument.
 */
static char *gds___dirid(char *dirname)
{
   int    status, nf;
   long   device, inode; 
   struct stat statbuf;
   FILE   *f;
   static char result[50];
   char   filename[128];
    
   strcpy(filename,dirname);
   strcat(filename,IDENTFILE);
   status = stat(dirname, &statbuf);         /* check directory accessibility */
   if (status) return NULL;
   
/* Try to read directory identification file.
 * If the inode number is this file is consistent with inode found by stat(),
 * assume that this is a correct ident file.
 */
   f = fopen(filename,"r");
   if (f) {
      nf = xscanf(f, "%d %d",&device, &inode);
      fclose(f);
      if ((nf == 2) && (statbuf.st_ino == inode)) {
         sprintf(result,"%ld_%ld",device,inode);
         return result;
      }
   }

/* Ident file non-(existent|consistent): use information from stat() and
 * try to re-write ident file.
 */
   device = statbuf.st_dev;
   inode  = statbuf.st_ino;
   f = fopen(filename,"w");
   if (f) {
      fprintf(f,"%ld:%ld", device, inode);
      fclose(f);
   }
   sprintf(result,"%ld_%ld",device,inode);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 RealName                                   */
/* -------------------------------------------------------------------------- */
/* RealName() resolves any symbolic links in the argument name and returns
 * the resulting name.
 */
static char *RealName(char *name)
{
   int    i;
   char   linkbuffer[LNBLEN]; 
   int    linklen;
   char  *result;
   
   result = NNew(strlen(name)+strlen(DESCR)+1,char);
   strcpy(result,name); strcat(result,DESCR);
   while ((linklen = readlink(result,linkbuffer,LNBLEN)) >0) {
      if (linkbuffer[0]=='/') {
         result[0] = '\0';                    /* link is absolute path */
      } else {
         for (i=strlen(result)-1; i>0; i--) {
            if (result[i-1]=='/') {
               result[i] = '\0';
               break;
            }
         }                                    /* strip name of symbolic link  */
      }
      result = realloc(result,strlen(result)+linklen+1);
      linkbuffer[linklen] = '\0';          /* append name pointed to       */
      strcat(result,linkbuffer);
   }
   for (i=strlen(result)-1; i>0; i--) {
      if (result[i]=='.') {
         result[i] = '\0';
         break;
      }
   }                                       /* strip off file name extension */
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 StrDup                                     */
/* -------------------------------------------------------------------------- */
/* StrDup() makes a duplicate if the argument string.
 */
static char *StrDup (char *orig)
{
   char *result=NNew(strlen(orig)+1,char);
   
   strcpy(result,orig);
   return result;
}

/* -------------------------------------------------------------------------- */
/*                                 gds___server.h                             */
/* -------------------------------------------------------------------------- */
/*  Declarations and prototypes.

#>gds___server.h
#if !defined(_gds___server_h_)
#define _gds___server_h_
typedef void* gdsserver;
extern gdsserver gds___server(char *name);
extern int       gds___srvreq(gdsserver, void*, int, int*);
extern int       gds___srvsnd(gdsserver id, void* data, int nbytes);
extern int       gds___srvrcv(gdsserver id, void* data, int nbytes);
extern char     *gds___srvnam(gdsserver id, char *fullname);
extern void      gds___srvcls(void);
extern int       gds___srverr(void);
#endif
#<
*/

