/* namedos.c : NameSpace DOS Services, mars_nwe
 *
 * Adds DOS 8.3 alias generation/resolution for long filenames on DOS
 * namespace clients.  This file was originally derived from nameos2.c,
 * but DOS needs synthetic aliases while OS/2 can address long names.
 */

#include "net.h"
#include <dirent.h>
#include <utime.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#ifndef LINUX
#include <errno.h>
#endif

#include "nwfname.h"
#include "nwvolume.h"
#include "connect.h"
#include "nwfile.h"
#include "unxfile.h"
#include "namedos.h"

#if WITH_NAME_SPACE_CALLS

#define MAX_NAME_DOS_CACHE   0

static int dos_is_alias_char(int c)
{
  if (isalnum((unsigned char)c)) return(1);
  switch (c) {
    case '$': case '%': case '\'': case '-': case '_': case '@':
    case '~': case '`': case '!': case '(': case ')': case '{':
    case '}': case '^': case '#': case '&':
      return(1);
    default:
      return(0);
  }
}

static int dos_alias_char(int c)
{
  if (c >= 'a' && c <= 'z') c -= ('a' - 'A');
  if (dos_is_alias_char(c)) return(c);
  return('_');
}

int dos_is_83_name(uint8 *name, int options)
{
  uint8 *ss=name;
  int len=0;
  int pf=0;

  if (!name || !*name) return(0);
  if (!strcmp((char*)name, ".") || !strcmp((char*)name, "..")) return(1);

  for (; *ss; ss++){
    if (*ss == '.') {
      if (pf++) return(0);       /* no second dot */
      if (!len) return(0);       /* no empty base */
      len=0;
    } else {
      if (!dos_is_alias_char(*ss)) return(0);
      ++len;
      if ((pf && len > 3) || len > 8) return(0);
      if (!(options & VOL_OPTION_IGNCASE)){
        if (options & VOL_OPTION_DOWNSHIFT){
          if (*ss >= 'A' && *ss <= 'Z') return(0);
        } else {
          if (*ss >= 'a' && *ss <= 'z') return(0);
        }
      }
    }
  }
  return(len > 0);
}

static void build_dos_83_raw(uint8 *real_name, uint8 *alias, int alias_len)
{
  uint8 base[9];
  uint8 ext[4];
  uint8 *dot=NULL;
  uint8 *s;
  int bl=0, el=0;

  memset(base, 0, sizeof(base));
  memset(ext, 0, sizeof(ext));

  if (!real_name || !*real_name) {
    strmaxcpy(alias, "_", alias_len-1);
    return;
  }

  if (real_name[0] != '.')
    dot=(uint8*)strrchr((char*)real_name, '.');

  for (s=real_name; *s && s != dot; s++) {
    if (bl < 8) base[bl++] = (uint8)dos_alias_char(*s);
  }
  if (!bl) base[bl++] = '_';

  if (dot) {
    for (s=dot+1; *s; s++) {
      if (el < 3) ext[el++] = (uint8)dos_alias_char(*s);
    }
  }

  if (el)
    snprintf((char*)alias, alias_len, "%s.%s", base, ext);
  else
    snprintf((char*)alias, alias_len, "%s", base);
}

static int alias_base_equal(uint8 *a, uint8 *b)
{
  uint8 aa[14], bb[14];
  uint8 *pa, *pb;
  strmaxcpy(aa, a, sizeof(aa)-1);
  strmaxcpy(bb, b, sizeof(bb)-1);
  pa=(uint8*)strchr((char*)aa, '.');
  pb=(uint8*)strchr((char*)bb, '.');
  if (pa) *pa='\0';
  if (pb) *pb='\0';
  return(!strcmp((char*)aa, (char*)bb));
}

static char dos_digit(int n)
{
  n &= 0x0f;
  return((n < 10) ? ('0' + n) : ('A' + n - 10));
}

static void apply_collision_number(uint8 *alias, int collision)
{
  uint8 *dot=(uint8*)strchr((char*)alias, '.');
  uint8 ext[5];
  uint8 base[9];
  int bl;

  memset(ext, 0, sizeof(ext));
  memset(base, 0, sizeof(base));
  if (dot) {
    strmaxcpy(ext, dot, sizeof(ext)-1);
    *dot='\0';
  }
  strmaxcpy(base, alias, sizeof(base)-1);
  bl=strlen((char*)base);
  if (bl >= 8) bl=7;
  base[bl++] = dos_digit(collision);
  base[bl] = '\0';
  snprintf((char*)alias, 14, "%s%s", base, ext);
}

int build_dos_83_alias(int options, uint8 *parent_unix,
                       uint8 *real_name, ino_t inode,
                       uint8 *alias, int alias_len)
{
  uint8 raw[14];
  int collisions=0;
  DIR *d;

  (void)inode;

  if (alias_len < 2) return(0);
  *alias='\0';

  if (dos_is_83_name(real_name, options)) {
    strmaxcpy(alias, real_name, alias_len-1);
    up_fn(alias);
    return(strlen((char*)alias));
  }

  build_dos_83_raw(real_name, raw, sizeof(raw));
  strmaxcpy(alias, raw, alias_len-1);

  if (parent_unix && *parent_unix && NULL != (d=opendir((char*)parent_unix))) {
    struct dirent *de;
    while ((de=readdir(d)) != NULL) {
      uint8 other[14];
      if (!de->d_ino) continue;
      if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue;
      if (!strcmp(de->d_name, (char*)real_name)) continue;
      build_dos_83_raw((uint8*)de->d_name, other, sizeof(other));
      if (!strcmp((char*)other, (char*)raw)) {
        if (strcmp(de->d_name, (char*)real_name) < 0)
          collisions++;
      }
      /* A real 8.3 name that equals the raw alias consumes that alias. */
      if (dos_is_83_name((uint8*)de->d_name, options)) {
        uint8 native[14];
        strmaxcpy(native, (uint8*)de->d_name, sizeof(native)-1);
        up_fn(native);
        if (!strcmp((char*)native, (char*)raw)) collisions++;
      }
    }
    closedir(d);
  }

  if (collisions > 0) {
    strmaxcpy(alias, raw, alias_len-1);
    apply_collision_number(alias, collisions-1);
  }

  up_fn(alias);
  return(strlen((char*)alias));
}

static int dos_alias_match(uint8 *a, uint8 *b)
{
  uint8 aa[14], bb[14];
  strmaxcpy(aa, a, sizeof(aa)-1);
  strmaxcpy(bb, b, sizeof(bb)-1);
  up_fn(aa);
  up_fn(bb);
  return(!strcmp((char*)aa, (char*)bb));
}

void mangle_dos_name(NW_VOL *vol, uint8 *unixname, uint8 *pp, int len)
{
  uint8 wanted[14];
  uint8 rest[512];
  uint8 *slash;
  uint8 *parent_slash;
  DIR *d;

  if (!vol || !unixname || !pp || !*pp) return;
  if (len <= 1) return;

  slash=(uint8*)strchr((char*)pp, '/');
  memset(rest, 0, sizeof(rest));
  if (slash) {
    strmaxcpy(rest, slash, sizeof(rest)-1);
    *slash='\0';
  }

  strmaxcpy(wanted, pp, sizeof(wanted)-1);
  up_fn(wanted);

  parent_slash = (pp > unixname && *(pp-1) == '/') ? pp-1 : NULL;
  if (parent_slash) *parent_slash='\0';

  d=opendir((char*)unixname);
  if (d) {
    struct dirent *de;
    while ((de=readdir(d)) != NULL) {
      uint8 alias[14];
      if (!de->d_ino) continue;
      if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue;
      build_dos_83_alias(vol->options, unixname, (uint8*)de->d_name,
                         de->d_ino, alias, sizeof(alias));
      if (dos_alias_match(alias, wanted)) {
        if (parent_slash) *parent_slash='/';
        snprintf((char*)pp, len, "%s%s", de->d_name, rest);
        closedir(d);
        if (*rest) {
          uint8 *next=(uint8*)strchr((char*)pp, '/');
          if (next && *(next+1))
            mangle_dos_name(vol, unixname, next+1, len - (int)((next+1) - pp));
        }
        return;
      }
    }
    closedir(d);
  }

  if (parent_slash) *parent_slash='/';
  if (slash) *slash='/';
}

static inline int get_n_p(uint8 **p)
{
  int pc=**p;
  (*p)++;
  if (pc == '\\') {
    pc=**p;
    (*p)++;
  } else if (pc == 255) {
    pc=**p;
    (*p)++;
    switch (pc) {
      case 0xaa :
      case '*'  : return(3000); /* star  */

      case 0xae :
      case '.'  : return(1000); /* point */

      case 0xbf :
      case '?'  : return(2000); /*  ? */

      default   : break;
    }
  } 
  return(pc);
}

static int ns_fn_dos_match(uint8 *s, uint8 *p, int soptions)
/* OS/2 name matching routine */
{
  int  pc, sc;
  uint state = 0;
  int  anf, ende;
  int  not = 0;
  uint found = 0;
  while ( (pc = get_n_p(&p)) != 0) {
    if (!(soptions & VOL_OPTION_IGNCASE)) {
      if (soptions & VOL_OPTION_DOWNSHIFT){   /* only downshift chars */
        if (*s >= 'A' && *s <= 'Z') return(0);
      } else {
        if (*s >= 'a' && *s <= 'z') return(0);
      }
    }
    switch (state){
      case 0 :
      switch  (pc) {
        case '.' :
        case 1000:  if (*s && ('.' != *s++))
                      return(0);
                     break;

        case '?' :
        case 2000:  if (!*s) return(0);
                    ++s;
                    break;

        case '*' :
        case 3000:  {
                      uint8 *pp;
                      int   np;
                      if (!*p) return(1); /* last star */
                      while (*s) {
                        if (ns_fn_dos_match(s, p, soptions) == 1) return(1);
                        else if (*s=='.') {  
                          pp=p;
                          if (!get_n_p(&p) || !get_n_p(&p)) 
                            return(0);
                          p=pp;
                        }
                        ++s;
                      }
                      pp=p;
                      np=get_n_p(&p);
                      p=pp;
                      if (np == '.' || np == 1000) 
                        return(ns_fn_dos_match(s, p, soptions));
                    }
                    return(0);

        case '[' :  if ( (*p == '!') || (*p == '^') ){
                       ++p;
                       not = 1;
                     }
                     state = 1;
                     continue;

        default  :  if (soptions & VOL_OPTION_IGNCASE) {
                      if (!dfn_imatch(*s, pc))
                           return(0);
                    } else if (pc != *s) return(0);
                    ++s;
                    break;

      }  /* switch */
      break;

      case   1  :   /*  Bereich von Zeichen  */
        sc = *s++;
        found = not;
        if (!sc) return(0);
        do {
          if (pc == '\\') pc = *(p++);
          if (!pc) return(0);
          anf = pc;
          if (*p == '-' && *(p+1) != ']'){
            ende = *(++p);
            p++;
          }
          else ende = anf;
          if (found == not) { /* only if not found */
            if (anf == sc || (anf <= sc && sc <= ende))
               found = !not;
          }
        } while ((pc = *(p++)) != ']');
        if (! found ) return(0);
        not   = 0;
        found = 0;
        state = 0;
        break;

      default :  break;
    }  /* switch */
  } /* while */
  if (*s=='.' && *(s+1)=='\0') return(1);
  return ( (*s) ? 0 : 1);
}
#endif
