/*
 * PDBDIR.C - provides a directory capability for PDBLib
 *
 * Source Version: 9.0
 * Software Release #92-0043
 *
 */

#include "cpyright.h"

#include "pdb.h"

typedef struct s_file_static file_static;

struct s_file_static
   {char cwd[MAXLINE];
    char outname[MAXLINE];};

#ifdef HAVE_THREADS

#define CWD(x)      _PD_dir_static[x].cwd
#define OUTNAME(x)  _PD_dir_static[x].outname

#define INCR(x)                                                              \
    {SC_LOCKON(PD_dir_lock);                                                 \
     x++;                                                                    \
     SC_LOCKOFF(PD_dir_lock);}

static file_static
 *_PD_dir_static = NULL;

SC_THREAD_LOCK(PD_dir_lock);

#else

#define CWD(x)     _PD_dir_static.cwd
#define OUTNAME(x) _PD_dir_static.outname

#define INCR(x) x++

static file_static
 _PD_dir_static;

#endif

int
 _PD_link_attribute = FALSE;

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* PD_CD - change the current working directory
 *       - the directory may be specified by an absolute or relative path.
 */

int PD_cd(file, dirname)
   PDBfile *file;
   char *dirname;
   {char name[MAXLINE];
    syment *ep;
    SC_THREAD_ID(_t_index);

    PD_ERR(_t_index)[0] = '\0';

    if (file == NULL)
       {sprintf(PD_ERR(_t_index), "ERROR: BAD FILE ID - PD_CD\n");
        return(FALSE);};
     
    if (dirname == NULL)
       strcpy(name, "/");
    else
       {strcpy(name, _PD_fixname(file, dirname));
        if (name[strlen(name) - 1] != '/')
           strcat(name, "/");};

    ep = PD_inquire_entry(file, name, FALSE, NULL);
    if (ep == NULL)
       {if (dirname == NULL)
           return(FALSE);

        else
           {if (strcmp(name, "/") != 0)
               {name[strlen(name) - 1] = '\0';
                ep = PD_inquire_entry(file, name, FALSE, NULL);
                strcat(name, "/");};

            if (ep == NULL)
               {sprintf(PD_ERR(_t_index),
                        "ERROR: DIRECTORY %s NOT FOUND - PD_CD\n",
                        dirname);
                return(FALSE);};};};

    if (strcmp(ep->type, "Directory") != 0)
       {sprintf(PD_ERR(_t_index), "ERROR: BAD DIRECTORY %s - PD_CD\n", dirname);
        return(FALSE);}

    else
       {if (file->current_prefix)
           SFREE(file->current_prefix);
        file->current_prefix = SC_strsavef(name, "char*:PD_CD:name");}

    return(TRUE);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* PD_LN - create a link to a variable
 *       - This function simply installs a new symbol table entry. The new
 *       - syment is a copy of an already existing one, but with a new name.
 */

int PD_ln(file, oldname, newname)
   PDBfile *file;
   char *oldname, *newname;
   {syment *oldep;
    char newpath[MAXLINE], oldpath[MAXLINE], dirname[MAXLINE];
    char *nname, *s, **avl;
    SC_THREAD_ID(_t_index);

    PD_ERR(_t_index)[0] = '\0';

    if (file == NULL)
       {sprintf(PD_err, "ERROR: BAD FILE ID - PD_LN\n");
        return(FALSE);};

    if (oldname == NULL)
       {sprintf(PD_ERR(_t_index), "ERROR: VARIABLE NAME NULL - PD_LN\n");
        return(FALSE);};

/* if opened in read-only mode */
    if (file->mode == PD_OPEN)
       {sprintf(PD_ERR(_t_index), "ERROR: FILE OPENED READ-ONLY - PD_LN\n");
        return(FALSE);};
     
    strcpy(newpath, _PD_fixname(file, newname));
    nname = SC_firsttok(newpath, ".([ ");

    strcpy(oldpath, _PD_fixname(file, oldname));

/* make sure the directory in newname already exists */
    strcpy(dirname, nname);
    s = strrchr(dirname, '/');
    if ((s != NULL) && (PD_has_directories(file)))
       {s[1] = '\0';
        if (PD_inquire_entry(file, dirname, FALSE, NULL) == NULL)
           {dirname[strlen(dirname) - 1] = '\0';
            sprintf(PD_ERR(_t_index),
                    "ERROR: DIRECTORY %s DOES NOT EXIST - PD_LN\n", dirname);
            return(FALSE);};};

    oldep = PD_inquire_entry(file, oldpath, TRUE, NULL);
    if (oldep == NULL)
       {sprintf(PD_ERR(_t_index), "ERROR: VARIABLE %s NOT FOUND - PD_LN\n", oldname);
        return(FALSE);};
           
    _PD_e_install(nname, oldep, file->symtab, TRUE);

    if (_PD_link_attribute)
       {if (!PD_inquire_attribute(file, "LINK", NULL))
	   if (!PD_def_attribute(file, "LINK", "char *"))
	      {sprintf(PD_ERR(_t_index),
		       "ERROR: CANNOT CREATE LINK ATTRIBUTE - PD_LN\n");
	       return(FALSE);};

	avl  = FMAKE(char *, "PD_LN:avl");
	*avl = SC_strsavef(_PD_fixname(file, oldname),
			   "char*:PD_LN:fname");

	if (!PD_set_attribute(file, nname, "LINK", (byte *) avl))
	   {sprintf(PD_ERR(_t_index), "ERROR: CANNOT SET LINK ATTRIBUTE - PD_LN\n");
	    return(FALSE);};};

    return(TRUE);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* PD_LS - return a list of all variables and directories of the specified
 *       - type in the specified directory. If type is null, all types are
 *       - returned. If path is null, the root directory is searched.
 *       - Directories are terminated with a slash.
 */

char **PD_ls(file, path, type, num)
   PDBfile *file;
   char *path;
   char *type;
   int *num;
   {syment *ep;
    char **varlist, **outlist;
    char *name;
    char pattern[MAXLINE];
    int ne, nvars, i, has_dirs, head, pass;
    SC_THREAD_ID(_t_index);
     
    PD_ERR(_t_index)[0] = '\0';

    *num = 0;
    ne   = file->symtab->nelements;

    if (file == NULL)
       {sprintf(PD_ERR(_t_index), "ERROR: BAD FILE ID - PD_LS\n");
        return(NULL);};

    if (num == NULL)
       {sprintf(PD_ERR(_t_index), "ERROR: LAST ARGUMENT NULL - PD_LS\n");
        return(NULL);};

    if (ne == 0)
       return(NULL);
    
/* determine if file contains directories and
 * build a pattern which names must match e.g., '/dir/abc*'
 */
    if (PD_has_directories(file))
       {has_dirs = TRUE;
        if (path == NULL)
           {if (strcmp(PD_pwd(file), "/") == 0)
               strcpy(pattern, "/*");
            else
               sprintf(pattern, "%s/*", PD_pwd(file));}
        else
           {strcpy(pattern, _PD_fixname(file, path));
            ep = PD_inquire_entry(file, pattern, FALSE, NULL);
            if ((ep != NULL) && (strcmp(ep->type, "Directory") == 0))
               {if (pattern[strlen(pattern) - 1] == '/')
                   strcat(pattern, "*");
                else
                   strcat(pattern, "/*");}
            else
               {if (pattern[strlen(pattern) - 1] != '/')
                   {strcat(pattern, "/");
                    ep = PD_inquire_entry(file, pattern, FALSE, NULL);
                    if ((ep != NULL) && (strcmp(ep->type, "Directory") == 0))
                       strcat(pattern, "*");
                    else
                       pattern[strlen(pattern) - 1] = '\0';}
                else
                   {pattern[strlen(pattern) - 1] = '\0';
                    ep = PD_inquire_entry(file, pattern, FALSE, NULL);
                    if ((ep != NULL) && (strcmp(ep->type, "Directory") == 0))
                       strcat(pattern, "/*");
                    else
                       strcat(pattern, "/");};};};}
    else
       {has_dirs = FALSE;
        if (path == NULL)
           strcpy(pattern, "*");
        else
           strcpy(pattern, path);};
     
/* Generate the list of matching names. Note that this returns items which
 * are in the requested directory AND items which are in sub-directories of
 * the requested directory. In other words, all names which BEGIN with the
 * requested pattern are returned.
 */
    nvars = 0;
    outlist = FMAKE_N(char *, ne + 1, "PD_LS:outlist");
     
/*  The second pass is in case variables were written to the file before
 *  the first directory was created. Such variables lack an initial slash.
 */
    for (pass = 1; pass <= 2; pass++)

        {if (pass == 2)
            if (has_dirs && (strchr(pattern + 1, '/') == NULL))
               strcpy(pattern, pattern + 1);
            else
               break;

         varlist = SC_hash_dump(file->symtab, pattern);
     
         if ((varlist == NULL) || (varlist[0] == NULL))
            continue;
     
/* save only those variables which are IN the requested directory
 * (not in sub-directories), and are of the requested type
 */
         for (i = 0; (i < ne) && (varlist[i] != NULL); i++)
          
/* The entry '/' (the root directory) is a special case. It
 * is not a child of any directory, so should be ignored.
 */
             {if (strcmp("/", varlist[i]) == 0)
                 continue;
          
/* check to see if type of this variable matches request */
              if (type != NULL)
                 {ep = PD_inquire_entry(file, varlist[i], FALSE, NULL);
                  if (strcmp(ep->type, type) != 0)
		     continue;};

/*  If here, then variable is of right type. If this file has directories,
 *  check for any more slashes (/'s) in the name. If any are found, this
 *  is not a leaf element. NOTE: if directories are not used, slashes are
 *  valid charcters in file names.
 */
              if (has_dirs)
                 {if (pattern[0] != '/')
                     head = 0;
                  else
                     head = strlen(pattern) - strlen(strrchr(pattern, '/')) + 1;
                  name = &(varlist[i])[head];
                  if ((strlen(name) == 0) ||
                      ((pass == 2) && (name[0] == '/')) ||
                      ((strchr(name, '/') != NULL) &&
                       (strchr(name, '/') != ((name + strlen(name) - 1)))))
                     continue;}
              else
                 name = varlist[i];
          
/* variable is of right type and is a leaf in the requested directory */
              outlist[nvars++] = name;};};
     
         SFREE(varlist);
     
/* store a null string to terminate list (just a precaution) */
    outlist[nvars] = NULL;

    if (has_dirs)
       SC_string_sort(outlist, nvars);
     
    *num = nvars;
     
    return(outlist);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_WR_DIR - actually write the directory entry */

static int _PD_wr_dir(file, name)
   PDBfile *file;
   char *name;
   {int ret;
    int *dir;
    static int dir_num = 0;

    dir  = FMAKE(int, "_PD_WR_DIR:dir");
    *dir = dir_num;
    ret  = PD_write(file, name, "Directory", dir);

    SFREE(dir);

    if (ret)
       INCR(dir_num);

    return(ret);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* PD_DEF_DIR - define and initialize the directory machinery for
 *            - the given FILE
 */

int PD_def_dir(file)
   PDBfile *file;
   {int ret;

    if (PD_defncv(file, "Directory", 1, 0) == NULL)
       ret = FALSE;

/* write out the root directory */
    else
       {ret = _PD_wr_dir(file, "/");
	if (ret)
	   file->current_prefix = SC_strsavef("/",
					      "char*:PD_DEF_DIR:slash");};

    return(ret);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* PD_MKDIR - create a directory
 *          - the directory may be specified by an absolute or relative path
 */

int PD_mkdir(file, dirname)
   PDBfile *file;
   char *dirname;
   {int ret;
    char name[MAXLINE], head[MAXLINE];
    char *s;
    SC_THREAD_ID(_t_index);

    PD_ERR(_t_index)[0] = '\0';

    if (file == NULL)
       {sprintf(PD_ERR(_t_index), "ERROR: BAD FILE ID - PD_MKDIR\n");
        return(FALSE);};

    if (dirname == NULL)
       {sprintf(PD_ERR(_t_index), "ERROR: DIRECTORY NAME NULL - PD_MKDIR\n");
        return(FALSE);};
     
/* check that type "Directory" has been defined */
/* GOTCHA: this can go after  July 1999 */
    if (!PD_has_directories(file))
       {sprintf(PD_ERR(_t_index), "ERROR: DIRECTORIES UNDEFINED - PD_MKDIR\n");
        return(FALSE);};
/*       PD_def_dir(file); */

/* build an absolute pathname */
    strcpy(name, _PD_fixname(file, dirname));
    if (name[strlen(name) - 1] != '/')
       strcat(name, "/");

/* make sure this directory hasn't already been created */
    if (PD_inquire_entry(file, name, FALSE, NULL) != NULL)
       {sprintf(PD_ERR(_t_index),
                "ERROR: DIRECTORY %s ALREADY EXISTS - PD_MKDIR\n", name);
        return(FALSE);};

/* make sure the next higher level directory already exists */
    strcpy(head, name);
    head[strlen(head) - 1] = '\0';
    s = strrchr(head, '/');
    if (s != NULL)
       {s[1] = '\0';
        if (PD_inquire_entry(file, head, FALSE, NULL) == NULL)
           {head[strlen(head) - 1] = '\0';
            sprintf(PD_ERR(_t_index),
                    "ERROR: DIRECTORY %s DOES NOT EXIST - PD_MKDIR\n", head);
            return(FALSE);};};

/* write the directory variable */
    ret = _PD_wr_dir(file, name);

    return(ret);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* PD_PWD - return the current working directory */

char *PD_pwd(file)
   PDBfile *file;
   {SC_THREAD_ID(_t_index);

    PD_ERR(_t_index)[0] = '\0';
   
    if (file == NULL)
       {sprintf(PD_ERR(_t_index), "ERROR: BAD FILE ID - PF_PWD\n");
        return(NULL);};

    if ((file->current_prefix == NULL) ||
        (strcmp(file->current_prefix, "/") == 0))
       strcpy(CWD(_t_index), "/");

    else
       {strcpy(CWD(_t_index), file->current_prefix);
        CWD(_t_index)[strlen(CWD(_t_index)) - 1] = '\0';}

    return(CWD(_t_index));}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_FIXNAME - make full pathname from current working directory
 *                  - and the given pathname (absolute or relative)
 */

char *_PD_fixname(file, inname)
   PDBfile *file;
   char *inname;
   {char *node, *s;
    char tmpstr[MAXLINE];
    SC_THREAD_ID(_t_index);

    if ((file == NULL) || (inname == NULL))
       return(NULL);

    OUTNAME(_t_index)[0] = '\0';

    if (!PD_has_directories(file))

/* if no directories, just copy verbatim */
       strcpy(OUTNAME(_t_index), inname);

    else
          
/* Break path into slash-separated tokens.
 * Process each node individually.
 */
       {if (inname[0] != '/')
           strcpy(OUTNAME(_t_index), PD_pwd(file));
        strcpy(tmpstr, inname);
        node = (char *) SC_strtok(tmpstr, "/", s);
          
        while (node != NULL)
           {if (strcmp(".",  node) == 0)
               {/* no-op */}

/* go up one level, unless already at top */
            else if (strcmp("..", node) == 0)
               {if (strcmp("/", OUTNAME(_t_index)) != 0)
                   {char  *s;
                    if (OUTNAME(_t_index)[strlen(OUTNAME(_t_index)) - 1] == '/')
                       OUTNAME(_t_index)[strlen(OUTNAME(_t_index)) - 1] = '\0';
                    s = strrchr(OUTNAME(_t_index), '/');
                    if (s != NULL)
                       s[0] = '\0';};}

/* append to end of current path */
            else
               {if (OUTNAME(_t_index)[strlen(OUTNAME(_t_index)) - 1] != '/')
                   strcat(OUTNAME(_t_index), "/");
                strcat(OUTNAME(_t_index), node);}
            node = (char *) SC_strtok(NULL, "/", s);};

        if ((inname[strlen(inname) - 1] == '/') &&
            (OUTNAME(_t_index)[strlen(OUTNAME(_t_index)) - 1] != '/'))
           strcat(OUTNAME(_t_index), "/");};

    if (OUTNAME(_t_index)[0] == '\0')
       strcpy(OUTNAME(_t_index), "/");

    return(OUTNAME(_t_index));}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PD_DIR_PINIT - initialize the file static variables for
 *                 parallel execution.
 */

void _PD_dir_pinit()
  {

#ifdef HAVE_THREADS

   if (_PD_dir_static == NULL)
      _PD_dir_static = NMAKE_N(file_static, _PD_nthreads,
			       "_PD_DIR_PINIT:_PD_dir_static");
#endif

   return;}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

