/* vmsify.c -- Module for vms <-> unix file name conversion
Copyright (C) 1996-2014 Free Software Foundation, Inc.
This file is part of GNU Make.

GNU Make is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3 of the License, or (at your option) any later
version.

GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program.  If not, see <http://www.gnu.org/licenses/>.  */

/* Written by Klaus Kämpf (kkaempf@progis.de)
   of proGIS Software, Aachen, Germany */


#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "makeint.h"

#if VMS
#include <unixlib.h>
#include <stdlib.h>
#include <jpidef.h>
#include <descrip.h>
#include <uaidef.h>
#include <ssdef.h>
#include <starlet.h>
#include <lib$routines.h>
/* Initialize a string descriptor (struct dsc$descriptor_s) for an
   arbitrary string.   ADDR is a pointer to the first character
   of the string, and LEN is the length of the string. */

#define INIT_DSC_S(dsc, addr, len) do { \
  (dsc).dsc$b_dtype = DSC$K_DTYPE_T;    \
  (dsc).dsc$b_class = DSC$K_CLASS_S;    \
  (dsc).dsc$w_length = (len);           \
  (dsc).dsc$a_pointer = (addr);         \
} while (0)

/* Initialize a string descriptor (struct dsc$descriptor_s) for a
   NUL-terminated string.  S is a pointer to the string; the length
   is determined by calling strlen(). */

#define INIT_DSC_CSTRING(dsc, s) INIT_DSC_S(dsc, s, strlen(s))
#endif

/*
  copy 'from' to 'to' up to but not including 'upto'
  return 0 if eos on from
  return 1 if upto found

  return 'to' at last char + 1
  return 'from' at match + 1 or eos if no match

  if as_dir == 1, change all '.' to '_'
  else change all '.' but the last to '_'
*/

static int
copyto (char **to, const char **from, char upto, int as_dir)
{
  const char *s;

  s = strrchr (*from, '.');

  while (**from)
    {
      if (**from == upto)
	{
	  do
	    {
	      (*from)++;
	    }
	  while (**from == upto);
	  return 1;
	}
      if (**from == '.')
	{
	  if ((as_dir == 1)
	      || (*from != s))
	    **to = '_';
	  else
	    **to = '.';
	}
      else
	{
#ifdef HAVE_CASE_INSENSITIVE_FS
	  if (isupper ((unsigned char)**from))
	    **to = tolower ((unsigned char)**from);
	  else
#endif
	    **to = **from;
	}
      (*to)++;
      (*from)++;
    }

  return 0;
}


/*
  get translation of logical name

*/

static char *
trnlog (const char *name)
{
  int stat;
  static char reslt[1024];
  $DESCRIPTOR (reslt_dsc, reslt);
  short resltlen;
  struct dsc$descriptor_s name_dsc;
  char *s;

  /*
   * the string isn't changed, but there is no string descriptor with
   * "const char *dsc$a_pointer"
   */
  INIT_DSC_CSTRING (name_dsc, (char *)name);

  stat = lib$sys_trnlog (&name_dsc, &resltlen, &reslt_dsc);

  if ((stat&1) == 0)
    {
      return "";
    }
  if (stat == SS$_NOTRAN)
    {
      return "";
    }
  reslt[resltlen] = '\0';

  s = xmalloc (resltlen+1);
  strcpy (s, reslt);
  return s;
}

static char *
showall (char *s)
{
  static char t[512];
  char *pt;

  pt = t;
  if (strchr (s, '\\') == 0)
    return s;
  while (*s)
    {
      if (*s == '\\')
	{
	  *pt++ = *s;
	}
      *pt++ = *s++;
    }
  return pt;
}


enum namestate { N_START, N_DEVICE, N_OPEN, N_DOT, N_CLOSED, N_DONE };

/*
  convert unix style name to vms style
  type = 0 -> name is a full name (directory and filename part)
  type = 1 -> name is a directory
  type = 2 -> name is a filename without directory

  The following conversions are applied
			(0)		(1)			(2)
	input		full name	dir name		file name

1	./		<cwd>		[]			<current directory>.dir
2	../		<home of cwd>	<home of cwd>		<home of cwd>.dir

3	//		<dev of cwd>:	<dev of cwd>:[000000]	<dev of cwd>:000000.dir
4	//a		a:		a:			a:
5	//a/		a:		a:			a:000000.dir

9	/		[000000]	[000000]		000000.dir
10	/a		[000000]a	[a]			[000000]a
11	/a/		[a]		[a]			[000000]a.dir
12	/a/b		[a]b		[a.b]			[a]b
13	/a/b/		[a.b]		[a.b]			[a]b.dir
14	/a/b/c		[a.b]c		[a.b.c]			[a.b]c
15	/a/b/c/		[a.b.c]		[a.b.c]			[a.b]c.dir

16	a		a		[.a]			a
17	a/		[.a]		[.a]			a.dir
18	a/b		[.a]b		[.a.b]			[.a]b
19	a/b/		[.a.b]		[.a.b]			[.a]b.dir
20	a/b/c		[.a.b]c		[.a.b.c]		[.a.b]c
21	a/b/c/		[.a.b.c]	[.a.b.c]		[.a.b]c.dir

22	a.b.c		a_b.c		[.a_b_c]		a_b_c.dir

23	[x][y]z		[x.y]z		[x.y]z			[x.y]z
24	[x][.y]z	[x.y]z		[x.y]z			[x.y]z

25  filenames with '$'  are left unchanged if they contain no '/'
25  filenames with ':' are left unchanged
26  filenames with a single pair of '[' ']' are left unchanged

  The input string is not written to.  The result is also const because
  it's a static buffer; we don't want to change it.
*/

const char *
vmsify (const char *name, int type)
{
/* max 255 device
   max 39 directory
   max 39 filename
   max 39 filetype
   max 5 version
*/
/* todo: VMSMAXPATHLEN is defined for ODS2 names: it needs to be adjusted. */
#define VMSMAXPATHLEN 512

  enum namestate nstate;
  static char vmsname[VMSMAXPATHLEN+1];
  const char *fptr;
  const char *t;
  char *vptr;
  int as_dir;
  int count;
  const char *s;
  const char *s1;
  const char *s2;

  if (name == 0)
    return 0;
  fptr = name;
  vptr = vmsname;
  nstate = N_START;

  /* case 25a */
  t = strpbrk (name, "$:");

  if (t != 0)
    {
//      const char *s1;
//      const char *s2;

      if (type == 1)
        {
          s1 = strchr (t+1, '[');
          s2 = strchr (t+1, ']');
        }

      if (*t == '$')
        {
          if (strchr (name, '/') == 0)
            {
              strcpy (vmsname, name);
              if ((type == 1) && (s1 != 0) && (s2 == 0))
                strcat (vmsname, "]");
              return vmsname;
            }
        }
      else
        {
          strcpy (vmsname, name);
          if ((type == 1) && (s1 != 0) && (s2 == 0))
            strcat (vmsname, "]");
          return vmsname;
        }
    }

  /* case 26 */
  t = strchr (name, '[');

  if (t != 0)
    {
//      const char *s;
//      const char *s1 = strchr (t+1, '[');
      s1 = strchr (t+1, '[');
      if (s1 == 0)
	{
          strcpy (vmsname, name);
	  if ((type == 1) && (strchr (t+1, ']') == 0))
            strcat (vmsname, "]");
          return vmsname;
	}
      s1--;
      if (*s1 != ']')
	{
          strcpy (vmsname, name);
	  return vmsname;		/* not ][, keep unchanged */
	}

      /* we have ][ */

      s = name;

      /* s  -> starting char
	 s1 -> ending ']'  */
      do
	{
	  strncpy (vptr, s, s1-s);	/* copy up to but not including ']' */
	  vptr += s1-s;
	  if (*s1 == 0)
	    break;
	  s = s1 + 1;			/* s -> char behind ']' */
	  if (*s != '[')		/* was '][' ? */
	    break;			/* no, last ] found, exit */
	  s++;
	  if (*s != '.')
	    *vptr++ = '.';
	  s1 = strchr (s, ']');
	  if (s1 == 0)			/* no closing ] */
	    s1 = s + strlen (s);
	}
      while (1);

      *vptr++ = ']';

      fptr = s;

    }
  else		/* no [ in name */
    {
      int state = 0;
      int rooted = 1;	/* flag if logical is rooted, else insert [000000] */

      do
	{
      switch (state)
	{
	  case 0:				/* start of loop */
	    if (*fptr == '/')
	      {
		fptr++;
		state = 1;
	      }
	    else if (*fptr == '.')
	      {
		fptr++;
		state = 10;
	      }
	    else
	      state = 2;
	    break;

	  case 1:				/* '/' at start */
	    if (*fptr == '/')
	      {
		fptr++;
		state = 3;
	      }
	    else
	      state = 4;
	    break;

	  case 2:				/* no '/' at start */
            {
            const char *s = strchr (fptr, '/');
	    if (s == 0)			/* no '/' (16) */
	      {
		if (type == 1)
		  {
		    strcpy (vptr, "[.");
		    vptr += 2;
		  }
		copyto (&vptr, &fptr, 0, (type==1));
		if (type == 1)
		  *vptr++ = ']';
		state = -1;
	      }
	    else			/* found '/' (17..21) */
	      {
		if ((type == 2)
		    && (*(s+1) == 0))	/* 17(2) */
		  {
		    copyto (&vptr, &fptr, '/', 1);
		    state = 7;
		  }
		else
		  {
		    strcpy (vptr, "[.");
		    vptr += 2;
		    copyto (&vptr, &fptr, '/', 1);
		    nstate = N_OPEN;
		    state = 9;
		  }
	      }
	    break;
            }

	  case 3:				/* '//' at start */
            {
//            const char *s;
//            const char *s1;
            char *vp;
	    while (*fptr == '/')	/* collapse all '/' */
	      fptr++;
	    if (*fptr == 0)		/* just // */
	      {
		char cwdbuf[VMSMAXPATHLEN+1];

		s1 = getcwd(cwdbuf, VMSMAXPATHLEN);
		if (s1 == 0)
		  {
                    vmsname[0] = '\0';
		    return vmsname;	/* FIXME, err getcwd */
		  }
		s = strchr (s1, ':');
		if (s == 0)
		  {
                    vmsname[0] = '\0';
		    return vmsname;	/* FIXME, err no device */
		  }
		strncpy (vptr, s1, s-s1+1);
		vptr += s-s1+1;
		state = -1;
		break;
	      }

	    s = vptr;

	    if (copyto (&vptr, &fptr, '/', 1) == 0)	/* copy device part */
	      {
		*vptr++ = ':';
		state = -1;
		break;
	      }
	    *vptr = ':';
	    nstate = N_DEVICE;
	    if (*fptr == 0)	/* just '//a/' */
	      {
		strcpy (vptr+1, "[000000]");
		vptr += 9;
		state = -1;
		break;
	      }
	    *vptr = 0;
				/* check logical for [000000] insertion */
	    vp = trnlog (s);
	    if (*vp != '\0')
	      {			/* found translation */
		for (;;)	/* loop over all nested logicals */
		  {
		    char *vp2 = vp + strlen (vp) - 1;
		    if (*vp2 == ':')	/* translation ends in ':' */
		      {
			vp2 = trnlog (vp);
			free (vp);
			if (*vp2 == 0)
			  {
			    rooted = 0;
			    break;
			  }
			vp = vp2;
			continue;	/* next iteration */
		      }
		    if (*vp2 == ']')	/* translation ends in ']' */
		      {
			if (*(vp2-1) == '.')	/* ends in '.]' */
			  {
			    if (strncmp (fptr, "000000", 6) != 0)
			      rooted = 0;
			  }
			else
			  {
			    strcpy (vmsname, s1);
			    vp = strchr (vmsname, ']');
			    *vp = '.';
			    nstate = N_DOT;
			    vptr = vp;
			  }
		      }
		    break;
		  }
		free (vp);
	      }
	    else
	      rooted = 0;

	    if (*vptr == 0)
	      {
		nstate = N_DEVICE;
	        *vptr++ = ':';
	      }
	    else
	      vptr++;

	    if (rooted == 0)
	      {
		nstate = N_DOT;
	        strcpy (vptr, "[000000.");
		vptr += 8;
		vp = vptr-1;
	      }
	    else
	      vp = 0;

            /* vp-> '.' after 000000 or NULL */

	    s = strchr (fptr, '/');
	    if (s == 0)
	      {				/* no next '/' */
		if (*(vptr-1) == '.')
		  *(vptr-1) = ']';
		else if (rooted == 0)
		  *vptr++ = ']';
		copyto (&vptr, &fptr, 0, (type == 1));
		state = -1;
		break;
	      }
	    else
	      {
		while (*(s+1) == '/')	/* skip multiple '/' */
		  s++;
	      }

	    if ((rooted != 0)
	        && (*(vptr-1) != '.'))
	      {
		*vptr++ = '[';
		nstate = N_DOT;
	      }
	    else
	      if ((nstate == N_DOT)
		 && (vp != 0)
		 && (*(s+1) == 0))
		{
		  if (type == 2)
		    {
		      *vp = ']';
		      nstate = N_CLOSED;
		    }
		}
	    state = 9;
	    break;
            }
	  case 4:				/* single '/' at start (9..15) */
	    if (*fptr == 0)
	      state = 5;
	    else
	      state = 6;
	    break;

	  case 5:				/* just '/' at start (9) */
	    if (type != 2)
	      {
	        *vptr++ = '[';
		nstate = N_OPEN;
	      }
	    strcpy (vptr, "000000");
	    vptr += 6;
	    if (type == 2)
	      state = 7;
	    else
	      state = 8;
	    break;

	  case 6:		/* chars following '/' at start 10..15 */
            {
            const char *s;
	    *vptr++ = '[';
	    nstate = N_OPEN;
	    s = strchr (fptr, '/');
	    if (s == 0)			/* 10 */
	      {
		if (type != 1)
		  {
		    strcpy (vptr, "000000]");
		    vptr += 7;
		  }
		copyto (&vptr, &fptr, 0, (type == 1));
		if (type == 1)
		  {
		    *vptr++ = ']';
		  }
		state = -1;
	      }
	    else			/* 11..15 */
	      {
		if ( (type == 2)
		   && (*(s+1) == 0))	/* 11(2) */
		  {
		    strcpy (vptr, "000000]");
		    nstate = N_CLOSED;
		    vptr += 7;
		  }
		copyto (&vptr, &fptr, '/', (*(vptr-1) != ']'));
		state = 9;
	      }
	    break;
            }

	  case 7:				/* add '.dir' and exit */
	    if ((nstate == N_OPEN)
		|| (nstate == N_DOT))
	      {
		char *vp = vptr-1;
		while (vp > vmsname)
		  {
		    if (*vp == ']')
		      {
			break;
		      }
		    if (*vp == '.')
		      {
			*vp = ']';
			break;
		      }
		    vp--;
		  }
	      }
	    strcpy (vptr, ".dir");
	    vptr += 4;
	    state = -1;
	    break;

	  case 8:				/* add ']' and exit */
	    *vptr++ = ']';
	    state = -1;
	    break;

	  case 9:			/* 17..21, fptr -> 1st '/' + 1 */
            {
            const char *s;
	    if (*fptr == 0)
	      {
		if (type == 2)
		  {
		    state = 7;
		  }
		else
		  state = 8;
		break;
	      }
	    s = strchr (fptr, '/');
	    if (s == 0)
	      {
		if (type != 1)
		  {
		    if (nstate == N_OPEN)
		      {
			*vptr++ = ']';
			nstate = N_CLOSED;
		      }
		    as_dir = 0;
		  }
		else
		  {
		    if (nstate == N_OPEN)
		      {
			*vptr++ = '.';
			nstate = N_DOT;
		      }
		    as_dir = 1;
		  }
	      }
	    else
	      {
		while (*(s+1) == '/')
		  s++;
		if ( (type == 2)
		    && (*(s+1) == 0))		/* 19(2), 21(2)*/
		  {
		    if (nstate != N_CLOSED)
		      {
			*vptr++ = ']';
			nstate = N_CLOSED;
		      }
		    as_dir = 1;
		  }
		else
		  {
		    if (nstate == N_OPEN)
		      {
			*vptr++ = '.';
			nstate = N_DOT;
		      }
		    as_dir = 1;
		  }
	      }
	    if ( (*fptr == '.')			/* check for '..' or '../' */
		&& (*(fptr+1) == '.')
		&& ((*(fptr+2) == '/')
		    || (*(fptr+2) == 0)) )
	      {
                char *vp;
		fptr += 2;
		if (*fptr == '/')
		  {
		    do
		      {
			fptr++;
		      }
		    while (*fptr == '/');
		  }
		else if (*fptr == 0)
		  type = 1;
		vptr--;				/* vptr -> '.' or ']' */
		vp = vptr;
		for (;;)
		  {
		    vp--;
		    if (*vp == '.')		/* one back */
		      {
			vptr = vp;
			nstate = N_OPEN;
			break;
		      }
		    if (*vp == '[')		/* top level reached */
		      {
			if (*fptr == 0)
			  {
			    strcpy (vp, "[000000]");
			    vptr = vp + 8;
			    nstate = N_CLOSED;
			    s = 0;
			    break;
			  }
			else
			  {
			    vptr = vp+1;
			    nstate = N_OPEN;
			    break;
			  }
		      }
		  }
	      }
	    else
	      {
		copyto (&vptr, &fptr, '/', as_dir);
		if (nstate == N_DOT)
		  nstate = N_OPEN;
	      }
	    if (s == 0)
	      {					/* 18,20 */
		if (type == 1)
		  *vptr++ = ']';
		state = -1;
	      }
	    else
	      {
		if (*(s+1) == 0)
		  {
		    if (type == 2)		/* 19,21 */
		      {
		        state = 7;
		      }
		    else
		      {
			*vptr++ = ']';
			state = -1;
		      }
		  }
	      }
	    break;
            }

	  case 10:				/* 1,2 first is '.' */
	    if (*fptr == '.')
	      {
		fptr++;
		state = 11;
	      }
	    else
	      state = 12;
	    break;

	  case 11:				/* 2, '..' at start */
	    count = 1;
	    if (*fptr != 0)
	      {
		if (*fptr != '/')		/* got ..xxx */
		  {
                    strcpy (vmsname, name);
		    return vmsname;
		  }
		do				/* got ../ */
		  {
		    fptr++;
		    while (*fptr == '/') fptr++;
		    if (*fptr != '.')
		      break;
		    if (*(fptr+1) != '.')
		      break;
		    fptr += 2;
		    if ((*fptr == 0)
			|| (*fptr == '/'))
		      count++;
		  }
		while (*fptr == '/');
	      }
	    {					/* got '..' or '../' */
              char *vp;
	      char cwdbuf[VMSMAXPATHLEN+1];

	      vp = getcwd(cwdbuf, VMSMAXPATHLEN);
	      if (vp == 0)
		{
                  vmsname[0] = '\0';
		  return vmsname;    /* FIXME, err getcwd */
		}
	      strcpy (vptr, vp);
	      vp = strchr (vptr, ']');
	      if (vp != 0)
		{
		  nstate = N_OPEN;
		  while (vp > vptr)
		    {
		      vp--;
		      if (*vp == '[')
			{
			  vp++;
			  strcpy (vp, "000000]");
			  state = -1;
			  break;
			}
		      else if (*vp == '.')
			{
			  if (--count == 0)
			    {
			      if (*fptr == 0)	/* had '..' or '../' */
				{
				  *vp++ = ']';
				  state = -1;
				}
			      else			/* had '../xxx' */
				{
				  state = 9;
				}
			      *vp = '\0';
			      break;
			    }
			}
		    }
		}
	      vptr += strlen (vptr);
	    }
	    break;

	  case 12:				/* 1, '.' at start */
	    if (*fptr != 0)
	      {
		if (*fptr != '/')
		  {
                    strcpy (vmsname, name);
		    return vmsname;
		  }
		while (*fptr == '/')
		  fptr++;
	      }

	    {
              char *vp;
	      char cwdbuf[VMSMAXPATHLEN+1];

	      vp = getcwd(cwdbuf, VMSMAXPATHLEN);
	      if (vp == 0)
		{
                  vmsname[0] = '\0';
		  return vmsname;    /*FIXME, err getcwd */
		}
	      strcpy (vptr, vp);
            }
            if (*fptr == 0)
              {
                state = -1;
                break;
              }
            else
              {
                char *vp = strchr (vptr, ']');
                if (vp == 0)
                  {
                    state = -1;
                    break;
                  }
                *vp = '\0';
                nstate = N_OPEN;
                vptr += strlen (vptr);
                state = 9;
              }
	    break;
	}

	}
      while (state > 0);


    }


  /* directory conversion done
     fptr -> filename part of input string
     vptr -> free space in vmsname
  */

  *vptr++ = 0;

  return vmsname;
}



/*
  convert from vms-style to unix-style

  dev:[dir1.dir2]	//dev/dir1/dir2/
*/

const char *
unixify (const char *name)
{
  static char piece[512];
  const char *s;
  char *p;

  if (strchr (name, '/') != 0)		/* already in unix style */
    {
      strcpy (piece, name);
      return piece;
    }

  p = piece;
  *p = 0;

  /* device part */

  s = strchr (name, ':');

  if (s != 0)
    {
      int l = s - name;
      *p++ = '/';
      *p++ = '/';
      strncpy (p, name, l);
      p += l;
    }

  /* directory part */

  *p++ = '/';
  s = strchr (name, '[');

  if (s != 0)
    {
      s++;
      switch (*s)
        {
	  case ']':		/* [] */
	    strcat (p, "./");
	    break;
	  case '-':		/* [- */
	    strcat (p, "../");
	    break;
	  case '.':
	    strcat (p, "./");	/* [. */
	    break;
	  default:
	    s--;
	    break;
        }
      s++;
      while (*s)
        {
	  if (*s == '.')
	    *p++ = '/';
	  else
	    *p++ = *s;
	  s++;
	  if (*s == ']')
	    {
	      s++;
	      break;
	    }
        }
      if (*s != 0)		/* more after ']' ?? */
        {
	  if (*(p-1) != '/')
	    *p++ = '/';
	  strcpy (p, s);		/* copy it anyway */
        }
    }

  else		/* no '[' anywhere */

    {
      *p++ = 0;
    }

  /* force end with '/' */

  if (*(p-1) != '/')
    *p++ = '/';
  *p = 0;

  return piece;
}

/* EOF */
