/*///---------------------------------------------------------------------\\\*/
/*// Copyright (c)               (_)/=\~/_(_)         All rights reserved. \\*/
/*/  2005-2006                (_)  /=\  ~/_  (_)        Released under the  \*/
/*\  Jim Peters            (_)    /=\    ~/_    (_)             GNU GPL v2  /*/
/*\\ http://uazu.net    (_) ____ /=\ ____ ~/_ ____ (_)      http://fsf.org //*/
/*\\\---------------------------------------------------------------------///*/

//
//	SMTree -- see SYMIRR.jot for docs
//   

#ifdef HEADER

typedef struct Dir Dir;

typedef struct SMTree SMTree;
struct SMTree {
   char *out;			// Output string, or 0
   Dir *dir;			// List of outstanding directories
   int baselen;			// Length of base pathname
};

#else

#include "all.h"

//
//	List of directories we're working through
//

struct Dir {
   Dir *nxt;
   int depth;		// Depth of directory (0 == top level)
   char *base;		// Base pathname for this directory (without trailing '/')
   List *list;		// List of filenames still to process
};
   

//
//	Load up a directory into memory and sort it into order.  Omits
//	certain entries (*~, _SYMIRR_BASE, *.NO_SYMIRR).  Returns an
//	error string, or 0.  Resulting list is returned at *rvp.
//

static char *
load_dir(char *path, List **rvp) {
   List *lp= 0;
   struct dirent *ent;

   DIR *dir= opendir(path);
   if (!dir) return StrDupF("Can't open directory: %s", path);

   while ((ent= readdir(dir))) {
      unsigned char *p= ent->d_name;
      if (!*p) continue;	// Blank filename?  (sanity check)
      while (*p >= 32) p++;
      if (*p) {
	 warn("Filename containing ASCII control characters IGNORED:\n"
	      "  %s/%s", path, ent->d_name);
	 continue;
      }
      if (ends_with(ent->d_name, "~")) continue;
      if (ends_with(ent->d_name, ".NO_SYMIRR")) continue;
      if (0 == strcmp(ent->d_name, ".")) continue;
      if (0 == strcmp(ent->d_name, "..")) continue;
      if (0 == strcmp(ent->d_name, "_SYMIRR_BASE")) continue;

      list_add(&lp, StrDup(ent->d_name));
   }
      
   closedir(dir);

   list_sort(&lp);
   *rvp= lp;
   return 0;
}

//
//	Push a new directory on to the stack
//

static void 
push_dir(SMTree *tr, char *base, int depth) {
   Dir *dir= ALLOC(Dir);
   char *err;
   err= load_dir(base, &dir->list);
   if (err) error("FATAL: %s", err);

   dir->base= StrDup(base);
   dir->nxt= tr->dir;
   dir->depth= depth;
   tr->dir= dir;
}

//
//	SMTree
//

SMTree *
smtree(char *base) {
   SMTree *tr= ALLOC(SMTree);
   push_dir(tr, base, 0);
   tr->baselen= strlen(base);
   return tr;
}

int 
smtree_do(SMTree *tr) {
   if (tr->out) return 0;	// Nothing to do until the output string is removed

 retry:
   if (!tr->dir) {
      tr->out= StrDup(SMEOF);
      return 0;
   }

   if (!tr->dir->list) {
      Dir *tmp= tr->dir;
      free(tmp->base);
      tr->dir= tmp->nxt;
      free(tmp);
      if (tr->dir) {
	 tr->out= StrDup("X");
      } else {
	 tr->out= StrDup("END");
      }
      return 1;
   }

   {
      char *p= list_pop(&tr->dir->list);
      char *tmp= StrDupF("%s/%s", tr->dir->base, p);
      int follow= tr->dir->depth < o_follow;
      char *dsc= smtree_lookup(tmp, follow, "");
      List *lp;
      int excl= 0;

      if (!dsc[0]) { free(tmp); goto retry; }	// Bad symlink or non-regular file
      if (0 == strcmp(dsc, "DEL")) 
	 error("Can't stat file (%s): %s", strerror(errno), tmp);

      // Check for exclusions
      if (tmp[tr->baselen]) 
	 for (lp= (dsc[0] == 'D') ? o_prune : o_excl; lp; lp= lp->nxt) {
	    excl= glob_match(lp->str, tmp + tr->baselen + 1);
	    if (excl) break;
	 }
      if (excl) { free(tmp); goto retry; }

      tr->out= StrDup(dsc);
      if (dsc[0] == 'D') push_dir(tr, tmp, tr->dir->depth+1); 
      free(tmp);
   }
   return 1;
}
   
void 
smtree_del(SMTree *tr) {
   while (tr->dir) {
      Dir *tmp=tr->dir;
      while (tmp->list) free(list_pop(&tmp->list));
      free(tmp->base);
      tr->dir= tmp->nxt;
      free(tmp);
   }
   free(tr->out);
   free(tr);
}

// Look up a given file or directory and return a pointer to its tree
// description in scratch.  'path' should be the full system pathname
// of the file/dir to inspect.  'flag' is "" or one of the update
// flags.  'follow' if set indicates that symbolic links should be
// followed.  The routine returns "DEL" if the file cannot be found,
// or "" if a symbolic link to be followed is broken or for
// non-regular files that should be skipped (both with a warning).
char *
smtree_lookup(char *path, int follow, char *flag) {
   // Cached uid/gids and names
   static uid_t c_uid;
   static gid_t c_gid;
   static char *c_uname, *c_gname; 	// Cached user/group names (StrDup'd)

   struct stat buf;
   int rv= follow ? stat(path, &buf) : lstat(path, &buf);
   char *nam= strrchr(path, '/');
   if (!nam) DIE;
   nam++;

   if (rv) {
      if (errno == ENOENT && 0 == lstat(path, &buf)) {
	 warn("Invalid symbolic link ignored: %s", path);
	 return "";
      }
      if (errno == ENOENT || errno == ENOTDIR) 
	 return "DEL";
      error("Error statting file (%s): %s", strerror(errno), path);
   }
   
   if (c_uid != buf.st_uid) {
      struct passwd *pw;
      if (c_uname) free(c_uname);
      c_uid= buf.st_uid;
      if (c_uid == my_uid)
	 c_uname= StrDup("_");
      else {
	 pw= getpwuid(c_uid);	// MEMDEBUG-IGNORE
	 c_uname= pw ? StrDup(pw->pw_name) : StrDupF("%d", (int)c_uid);	// MEMDEBUG-IGNORE
      }
   }

   if (c_gid != buf.st_gid) {
      struct group *gr;
      if (c_gname) free(c_gname);
      c_gid= buf.st_gid;
      if (c_gid == my_gid) 
	 c_gname= StrDup("_");
      else {
	 gr= getgrgid(c_gid);	// MEMDEBUG-IGNORE
	 c_gname= gr ? StrDup(gr->gr_name) : StrDupF("%d", (int)c_gid);	// MEMDEBUG-IGNORE
      }
   }
   
   if (S_ISLNK(buf.st_mode)) {
      // Symbolic link
      char txt[4096];
      int cnt= readlink(path, txt, sizeof(txt));
      if (cnt == 0) { warn("Invalid symbolic link ignored: %s", path); return ""; }
      if (cnt < 0) error("Error reading symlink (%s): %s", strerror(errno), path);
      if (cnt >= sizeof(txt)) error("Symlink text too long for buffer: %s", path);
      return StrF("F%s\tSYMLNK/%s/%s\t%s\t%u/#%08X", 
		  flag, c_uname, c_gname, nam, buf.st_size, do_hash(txt, cnt, 0));
   } else if (S_ISREG(buf.st_mode)) {
      // Normal file
      return StrF("F%s\t%06o/%s/%s\t%s\t%u/%08X", 
		  flag, buf.st_mode, c_uname, c_gname, nam, buf.st_size, buf.st_mtime);
   } else if (S_ISDIR(buf.st_mode)) {
      // Directory
      return StrF("D%s\t%06o/%s/%s\t%s", 
		  flag, buf.st_mode, c_uname, c_gname, nam);
   } 

   // Some other type of file -- give a warning and skip
   warn("Skipping non-regular file:\n  %s", path);
   return "";
}

#endif

/*///---------------------------------------------------------------------\\\*/
/*// )`( )()/)(`/\`//\`          (_)/=\~/_(_)           '/\\'/\')(\()( )'( \\*/
/*/ ()(')()\)(/`/\\ `         (_)  /=\  ~/_  (_)          ' //\'\)(/()(`)() \*/
/*\  )`()( )(/'            (_)    /=\    ~/_    (_)             `\)( )()'(  /*/
/*\\  ()()(`)(/         (_) ____ /=\ ____ ~/_ ____ (_)          \)(')()()  //*/
/*\\\---------------------------------------------------------------------///*/
