/* flag.c - Novell FLAG-like DOS utility, stage 1 */

#include "net.h"
#include <dos.h>

/*
 * FLAG v4b: NCP 87 namespace DOS info.
 *
 * ncpfs reference:
 *   ncp_ns_modify_entry_dos_info():
 *     subfunction 7, namespace DOS, search attrs SA_ALL,
 *     ModifyInformationMask, struct ncp_dos_info, handle/path.
 *
 * We use dirstyle=0 (short directory handle) against the current DOS
 * directory handle and a one-component DOS filename.
 */
#define FLAG_NW_NS_DOS       0x00
#define FLAG_SA_ALL          0x0006
#define FLAG_RIM_ATTRIBUTES  0x00000004UL

#define NWFA_RO 0x00000001UL
#define NWFA_H  0x00000002UL
#define NWFA_SY 0x00000004UL
#define NWFA_A  0x00000020UL
#define NWFA_S  0x00000080UL
#define NWFA_T  0x00001000UL
#define NWFA_RA 0x00004000UL
#define NWFA_WA 0x00008000UL
#define NWFA_P  0x00010000UL
#define NWFA_RI 0x00020000UL
#define NWFA_DI 0x00040000UL
#define NWFA_CI 0x00080000UL

static void flag_put_word_lh(uint8 *p, uint16 v)
{
  p[0] = (uint8)(v & 0xff);
  p[1] = (uint8)((v >> 8) & 0xff);
}

static void flag_put_dword_lh(uint8 *p, uint32 v)
{
  p[0] = (uint8)(v & 0xff);
  p[1] = (uint8)((v >> 8) & 0xff);
  p[2] = (uint8)((v >> 16) & 0xff);
  p[3] = (uint8)((v >> 24) & 0xff);
}

static uint32 flag_get_dword_lh(uint8 *p)
{
  return((uint32)p[0] |
         ((uint32)p[1] << 8) |
         ((uint32)p[2] << 16) |
         ((uint32)p[3] << 24));
}

static int flag_get_current_drive(void)
{
  REGS regs;

  regs.h.ah = 0x19;          /* DOS get current default drive */
  int86(0x21, &regs, &regs); /* AL = 0 for A:, 1 for B:, ... */

  return((int)regs.h.al);
}

static int flag_current_dhandle(uint8 *dhandle)
{
  uint8 connid = 0;
  uint8 flags = 0;
  int drive;

  drive = flag_get_current_drive();
  if (get_drive_info((uint8)drive, &connid, dhandle, &flags))
    return(-1);

  if (!connid || (flags & 0x80))
    return(-1);

  return(0);
}

static int flag_add_handle_path(uint8 *p, uint8 dhandle, char *name)
{
  int nlen;

  nlen = strlen(name);
  if (nlen > 255) nlen = 255;

  /*
   * handle/path:
   *   volume/handle byte
   *   dir base dword
   *   dirstyle byte (0 = short dir handle)
   *   path components: 1 component, length, bytes
   */
  *p++ = dhandle;
  flag_put_dword_lh(p, 0L); p += 4;
  *p++ = 0;                 /* dirstyle = handle */
  *p++ = 1;                 /* one path component */
  *p++ = (uint8)nlen;
  memcpy(p, name, nlen);
  p += nlen;

  return(1 + 4 + 1 + 1 + 1 + nlen);
}

static int flag_ncp87_obtain_attrs(char *name, uint32 *attrs)
{
  struct {
    uint16 len;
    uint8  data[320];
  } req;
  struct {
    uint16 len;
    uint8  data[128];
  } repl;
  uint8 dhandle = 0;
  uint8 *p;
  int hlen;

  if (flag_current_dhandle(&dhandle))
    return(-1);

  memset(&req, 0, sizeof(req));
  memset(&repl, 0, sizeof(repl));

  p = req.data;
  *p++ = 6;                 /* subfunction: obtain file/subdir info */
  *p++ = FLAG_NW_NS_DOS;    /* source namespace */
  *p++ = FLAG_NW_NS_DOS;    /* target namespace */
  flag_put_word_lh(p, FLAG_SA_ALL); p += 2;
  flag_put_dword_lh(p, FLAG_RIM_ATTRIBUTES); p += 4;
  hlen = flag_add_handle_path(p, dhandle, name);
  p += hlen;

  req.len = (uint16)(p - req.data);
  repl.len = sizeof(repl.data);

  neterrno = Net_Call(0xF257, &req, &repl);
  if (neterrno)
    return(-1);

  /*
   * With RIM_ATTRIBUTES only, ncpfs expects NSI_Attributes first.
   * First dword is the 32-bit Attributes field.
   */
  if (attrs)
    *attrs = flag_get_dword_lh(repl.data);

  return(0);
}

static int flag_ncp87_modify_attrs(char *name, uint32 attrs)
{
  struct {
    uint16 len;
    uint8  data[384];
  } req;
  struct {
    uint16 len;
    uint8  data[8];
  } repl;
  uint8 dhandle = 0;
  uint8 *p;
  int hlen;

  if (flag_current_dhandle(&dhandle))
    return(-1);

  memset(&req, 0, sizeof(req));
  memset(&repl, 0, sizeof(repl));

  p = req.data;
  *p++ = 7;                 /* subfunction: modify DOS info */
  *p++ = FLAG_NW_NS_DOS;
  *p++ = 0;                 /* reserved */
  flag_put_word_lh(p, FLAG_SA_ALL); p += 2;

  flag_put_dword_lh(p, FLAG_RIM_ATTRIBUTES); p += 4; /* modify mask */
  flag_put_dword_lh(p, attrs); p += 4;               /* Attributes */

  /*
   * Remaining ncp_dos_info fields.  Mask says only Attributes is valid,
   * so these should be ignored, but ncpfs still sends the full structure.
   */
  memset(p, 0, 34);
  p += 34;

  hlen = flag_add_handle_path(p, dhandle, name);
  p += hlen;

  req.len = (uint16)(p - req.data);
  repl.len = sizeof(repl.data);

  neterrno = Net_Call(0xF257, &req, &repl);
  if (neterrno)
    return(-1);

  return(0);
}


#ifndef _A_NORMAL
#define _A_NORMAL 0x00
#endif
#ifndef _A_RDONLY
#define _A_RDONLY 0x01
#endif
#ifndef _A_HIDDEN
#define _A_HIDDEN 0x02
#endif
#ifndef _A_SYSTEM
#define _A_SYSTEM 0x04
#endif
#ifndef _A_SUBDIR
#define _A_SUBDIR 0x10
#endif
#ifndef _A_ARCH
#define _A_ARCH 0x20
#endif

static int flag_same(char *a, char *b)
{
  while (*a || *b) {
    int ca = *a++;
    int cb = *b++;
    if (ca >= 'a' && ca <= 'z') ca -= 32;
    if (cb >= 'a' && cb <= 'z') cb -= 32;
    if (ca != cb) return(0);
  }
  return(1);
}

static void flag_help(void)
{
  fprintf(stdout, "USAGE:  FLAG [path [ option | [+|-] attribute(s) ] [SUB]]\n");
  fprintf(stdout, "\n");
  fprintf(stdout, "386 Attributes:\n");
  fprintf(stdout, "--------------\n");
  fprintf(stdout, "\n");
  fprintf(stdout, "RO Read Only\n");
  fprintf(stdout, "RW Read Write\n");
  fprintf(stdout, "S  Sharable\n");
  fprintf(stdout, "H  Hidden\n");
  fprintf(stdout, "Sy System\n");
  fprintf(stdout, "T  Transactional\n");
  fprintf(stdout, "P  Purge\n");
  fprintf(stdout, "A  Archive Needed\n");
  fprintf(stdout, "RA Read Audit\n");
  fprintf(stdout, "WA Write Audit\n");
  fprintf(stdout, "CI Copy Inhibit\n");
  fprintf(stdout, "DI Delete Inhibit\n");
  fprintf(stdout, "RI Rename Inhibit\n");
  fprintf(stdout, "\n");
  fprintf(stdout, "All  All\n");
  fprintf(stdout, "N    Normal\n");
  fprintf(stdout, "SUB\n");
}

static int flag_attr_mask(char *s, unsigned *setbits, unsigned *clearbits)
{
  int set = 1;
  char *p = s;

  if (*p == '+') {
    set = 1;
    p++;
  } else if (*p == '-') {
    set = 0;
    p++;
  }

  if (!*p) return(-1);

  if (flag_same(p, "RO")) {
    if (set) {
      *setbits |= (unsigned)(NWFA_RO | NWFA_DI | NWFA_RI);
    } else {
      *clearbits |= (unsigned)(NWFA_RO | NWFA_DI | NWFA_RI);
    }
  } else if (flag_same(p, "RW")) {
    *clearbits |= (unsigned)(NWFA_RO | NWFA_DI | NWFA_RI);
  } else if (flag_same(p, "S")) {
    if (set) *setbits |= (unsigned)NWFA_S;
    else *clearbits |= (unsigned)NWFA_S;
  } else if (flag_same(p, "H")) {
    if (set) *setbits |= (unsigned)NWFA_H;
    else *clearbits |= (unsigned)NWFA_H;
  } else if (flag_same(p, "SY") || flag_same(p, "SYS") || flag_same(p, "SYSTEM")) {
    if (set) *setbits |= (unsigned)NWFA_SY;
    else *clearbits |= (unsigned)NWFA_SY;
  } else if (flag_same(p, "T")) {
    if (set) *setbits |= (unsigned)NWFA_T;
    else *clearbits |= (unsigned)NWFA_T;
  } else if (flag_same(p, "P")) {
    if (set) *setbits |= (unsigned)NWFA_P;
    else *clearbits |= (unsigned)NWFA_P;
  } else if (flag_same(p, "A")) {
    if (set) *setbits |= (unsigned)NWFA_A;
    else *clearbits |= (unsigned)NWFA_A;
  } else if (flag_same(p, "RA")) {
    if (set) *setbits |= (unsigned)NWFA_RA;
    else *clearbits |= (unsigned)NWFA_RA;
  } else if (flag_same(p, "WA")) {
    if (set) *setbits |= (unsigned)NWFA_WA;
    else *clearbits |= (unsigned)NWFA_WA;
  } else if (flag_same(p, "CI")) {
    if (set) *setbits |= (unsigned)NWFA_CI;
    else *clearbits |= (unsigned)NWFA_CI;
  } else if (flag_same(p, "DI")) {
    if (set) *setbits |= (unsigned)NWFA_DI;
    else *clearbits |= (unsigned)NWFA_DI;
  } else if (flag_same(p, "RI")) {
    if (set) *setbits |= (unsigned)NWFA_RI;
    else *clearbits |= (unsigned)NWFA_RI;
  } else if (flag_same(p, "N") || flag_same(p, "NORMAL")) {
    *clearbits |= (unsigned)(NWFA_RO | NWFA_H | NWFA_SY | NWFA_A |
                             NWFA_S | NWFA_T | NWFA_P |
                             NWFA_RA | NWFA_WA | NWFA_CI | NWFA_DI | NWFA_RI);
  } else if (flag_same(p, "ALL")) {
    *setbits |= (unsigned)(NWFA_RO | NWFA_H | NWFA_SY | NWFA_A |
                           NWFA_S | NWFA_T | NWFA_P |
                           NWFA_RA | NWFA_WA | NWFA_CI | NWFA_DI | NWFA_RI);
  } else {
    fprintf(stderr, "Unknown attribute encountered in command line.\n");
    return(-1);
  }

  return(0);
}

static void flag_print_attrs(unsigned attr)
{
  /*
   * Novell order:
   *   RO/RW S A H Sy T P RA WA CI DI RI
   */
  fprintf(stdout, "[ ");
  fprintf(stdout, "%s ", (attr & NWFA_RO) ? "RO" : "Rw");
  fprintf(stdout, "%c ", (attr & NWFA_S) ? 'S' : '-');
  fprintf(stdout, "%c ", (attr & NWFA_A) ? 'A' : '-');
  fprintf(stdout, "%c ", (attr & NWFA_H) ? 'H' : '-');
  fprintf(stdout, "%s ", (attr & NWFA_SY) ? "Sy" : "-");
  fprintf(stdout, "%c ", (attr & NWFA_T) ? 'T' : '-');
  fprintf(stdout, "%c ", (attr & NWFA_P) ? 'P' : '-');
  fprintf(stdout, "%s ", (attr & NWFA_RA) ? "Ra" : "--");
  fprintf(stdout, "%s ", (attr & NWFA_WA) ? "Wa" : "--");
  fprintf(stdout, "%s ", (attr & NWFA_CI) ? "CI" : "--");
  fprintf(stdout, "%s ", (attr & NWFA_DI) ? "DI" : "--");
  fprintf(stdout, "%s ", (attr & NWFA_RI) ? "RI" : "--");
  fprintf(stdout, "]");
}

static void flag_display_one(char *name, unsigned attr)
{
  fprintf(stdout, "     %-23s  ", name);
  flag_print_attrs(attr);
  fprintf(stdout, "\n");
}

static int flag_has_wildcards(char *s)
{
  while (*s) {
    if (*s == '*' || *s == '?') return(1);
    s++;
  }
  return(0);
}

static int flag_list(char *pattern)
{
  struct find_t ff;
  unsigned findattr = _A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH;
  int found = 0;

  if (_dos_findfirst(pattern, findattr, &ff))
    return(-1);

  do {
    if (!(ff.attrib & _A_SUBDIR)) {
      uint32 nwattrs;

      if (flag_ncp87_obtain_attrs(ff.name, &nwattrs))
        nwattrs = (uint32)ff.attrib;

      flag_display_one(ff.name, (unsigned)nwattrs);
      found++;
    }
  } while (!_dos_findnext(&ff));

  return(found);
}

static int flag_apply(char *pattern, unsigned setbits, unsigned clearbits)
{
  struct find_t ff;
  unsigned findattr = _A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH;
  int shown = 0;

  if (_dos_findfirst(pattern, findattr, &ff))
    return(-1);

  do {
    uint32 attrs;
    uint32 newattrs;
    char fname[260];

    if (ff.attrib & _A_SUBDIR) continue;

    strmaxcpy(fname, ff.name, sizeof(fname) - 1);

    if (flag_ncp87_obtain_attrs(fname, &attrs))
      attrs = (uint32)ff.attrib;

    newattrs = (attrs | (uint32)setbits) & ~((uint32)clearbits);

    if (newattrs != attrs) {
      if (flag_ncp87_modify_attrs(fname, newattrs)) {
        unsigned dosattr = (unsigned)(newattrs & (_A_RDONLY|_A_HIDDEN|_A_SYSTEM|_A_ARCH));
        if (_dos_setfileattr(fname, dosattr)) {
          fprintf(stderr, "You don't have rights to change : %s\n", fname);
          continue;
        }
      }
    }

    if (flag_ncp87_obtain_attrs(fname, &attrs))
      attrs = newattrs;

    flag_display_one(fname, (unsigned)attrs);
    shown++;

  } while (!_dos_findnext(&ff));

  return(shown);
}

int func_flag(int argc, char *argv[], int mode)
{
  char *path = "*.*";
  int i;
  unsigned setbits = 0;
  unsigned clearbits = 0;
  int have_change = 0;
  int rc;

  (void)mode;

  if (argc > 1 && (flag_same(argv[1], "/?") || flag_same(argv[1], "-?") ||
                   flag_same(argv[1], "?"))) {
    flag_help();
    return(0);
  }

  if (argc > 1) {
    path = argv[1];
    if (flag_same(path, "SUB")) path = "*.*";
  }

  for (i = 2; i < argc; i++) {
    if (flag_same(argv[i], "SUB")) continue;

    rc = flag_attr_mask(argv[i], &setbits, &clearbits);
    if (rc < 0) return(1);
    if (rc > 0) continue;
    have_change = 1;
  }

  if (have_change) {
    rc = flag_apply(path, setbits, clearbits);
    if (rc < 0) {
      fprintf(stderr, "Files could not be found with pattern \"%s\"\n", path);
      return(1);
    }

    return(0);
  }

  rc = flag_list(path);
  if (rc < 0) {
    fprintf(stderr, "Files could not be found with pattern \"%s\"\n", path);
    return(1);
  }

  return(0);
}
