/*
 * mars-nwe-dosutils - NetWare/DOS utility tools.
 *
 * Copyright (C) 2026 Mario Fetka
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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/>.
 */

/*
 * Purpose: FLAG utility for displaying and changing NetWare DOS file attributes.
 * Depends on: net.h, ncpapi.h, netcall.c requester helpers, ncpapi.c namespace/NCP helpers, tools.c shared utility routines.
 */
/* flag.c - Novell FLAG-like DOS utility, stage 1 */

#include "net.h"
#include "ncpapi.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 FLAG_DM_ATTRIBUTES   0x00000002UL

#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 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;
  tool_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 connid = 0;
  uint8 dhandle = 0;
  uint8 *p;
  int hlen;

  if (tool_current_dhandle(&connid, &dhandle))
    return(-1);

  /*
   * Prefer the verified Client32 NCP87 path.  If it is not available,
   * fall back to the historical INT 21h/Net_Call path below.
   */
  if (ncp87_06_obtain_rim_attributes(name, (uint16)dhandle,
                                      attrs, NULL, NULL, NULL) == 0)
    return(0);

  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 */
  tool_put_word_lh(p, FLAG_SA_ALL); p += 2;
  tool_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 = tool_get_dword_lh(repl.data);

  return(0);
}


static int flag_ncp22_1e_obtain_attrs(char *name, uint32 *attrs)
{
  struct {
    uint16 len;
    uint8  func;
    uint8  dirhandle;
    uint8  search_attributes;
    uint8  searchsequence[4];
    uint8  namlen;
    uint8  name[12];
  } req;
  struct {
    uint16 len;
    uint8  data[128];
  } repl;
  uint8 connid = 0;
  uint8 dhandle = 0;
  uint8 namlen = 0;

  if (tool_current_dhandle(&connid, &dhandle))
    return(-1);

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

  if (tool_copy_ncp22_name(req.name, name, &namlen))
    return(-1);

  req.func = 0x1e;                         /* Scan directory */
  req.dirhandle = dhandle;
  req.search_attributes = 0x06;            /* hidden/system */
  U32_TO_BE32(0xffffffffUL, req.searchsequence);
  req.namlen = namlen;
  req.len = (uint16)(1 + 1 + 1 + 4 + 1 + namlen);
  repl.len = sizeof(repl.data);

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

  /*
   * NCP22/30 Scan Directory returns:
   *   dword searchsequence
   *   dword subdir
   *   dword attributes
   *   ...
   */
  if (attrs)
    *attrs = tool_get_dword_lh(repl.data + 8);

  return(0);
}

static int flag_ncp22_25_modify_attrs(char *name, uint32 attrs)
{
  struct {
    uint16 len;
    uint8  func;
    uint8  dirhandle;
    uint8  search_attributes;
    uint8  searchsequence[4];
    uint8  change_bits[4];
    uint8  subdir[4];
    uint8  attributes[4];
    uint8  uniqueid;
    uint8  flags;
    uint8  namespace;
    uint8  namlen;
    uint8  name[12];
    uint8  rest[104];
  } req;
  struct {
    uint16 len;
  } repl;
  uint8 connid = 0;
  uint8 dhandle = 0;

  if (tool_current_dhandle(&connid, &dhandle))
    return(-1);

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

  req.func = 0x25;                         /* Set directory/file information */
  req.dirhandle = dhandle;
  req.search_attributes = 0x06;            /* hidden/system */
  U32_TO_BE32(0xffffffffUL, req.searchsequence);
  U32_TO_32(FLAG_DM_ATTRIBUTES, req.change_bits);
  U32_TO_32(attrs, req.attributes);

  if (tool_copy_ncp22_name(req.name, name, &req.namlen))
    return(-1);

  req.len = sizeof(req) - sizeof(req.len);

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

  return(0);
}


static int flag_obtain_attrs(char *name, uint32 *attrs)
{
  if (!flag_ncp22_1e_obtain_attrs(name, attrs))
    return(0);

  return(flag_ncp87_obtain_attrs(name, attrs));
}

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 connid = 0;
  uint8 dhandle = 0;
  uint8 *p;
  int hlen;

  if (tool_current_dhandle(&connid, &dhandle))
    return(-1);

  /*
   * Prefer verified Client32 modify path.  The old INT 21h/F257 modify path
   * can hang under DOS Client32 for high FLAG bits such as T/P/DI/RI.
   */
  {
    uint16 actual = 0;
    uint16 hlo = 0;
    uint16 hhi = 0;
    if (!ncp87_07_modify_dos_attributes(name, (uint16)dhandle, attrs,
                                         &actual, &hlo, &hhi))
      return(0);
  }

  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 */
  tool_put_word_lh(p, FLAG_SA_ALL); p += 2;

  tool_put_dword_lh(p, FLAG_DM_ATTRIBUTES); p += 4;  /* modify mask: DM_ATTRIBUTES */
  tool_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 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, uint32 *setbits, uint32 *clearbits)
{
  int set = 1;
  char *p = s;

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

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

  if (tool_strsame(p, "RO")) {
    if (set) {
      *setbits |= (NWFA_RO | NWFA_DI | NWFA_RI);
    } else {
      *clearbits |= (NWFA_RO | NWFA_DI | NWFA_RI);
    }
  } else if (tool_strsame(p, "RW")) {
    *clearbits |= (NWFA_RO | NWFA_DI | NWFA_RI);
  } else if (tool_strsame(p, "S")) {
    if (set) *setbits |= NWFA_S;
    else *clearbits |= NWFA_S;
  } else if (tool_strsame(p, "H")) {
    if (set) *setbits |= NWFA_H;
    else *clearbits |= NWFA_H;
  } else if (tool_strsame(p, "SY") || tool_strsame(p, "SYS") || tool_strsame(p, "SYSTEM")) {
    if (set) *setbits |= NWFA_SY;
    else *clearbits |= NWFA_SY;
  } else if (tool_strsame(p, "T")) {
    if (set) *setbits |= NWFA_T;
    else *clearbits |= NWFA_T;
  } else if (tool_strsame(p, "P")) {
    if (set) *setbits |= NWFA_P;
    else *clearbits |= NWFA_P;
  } else if (tool_strsame(p, "A")) {
    if (set) *setbits |= NWFA_A;
    else *clearbits |= NWFA_A;
  } else if (tool_strsame(p, "RA")) {
    if (set) *setbits |= NWFA_RA;
    else *clearbits |= NWFA_RA;
  } else if (tool_strsame(p, "WA")) {
    if (set) *setbits |= NWFA_WA;
    else *clearbits |= NWFA_WA;
  } else if (tool_strsame(p, "CI")) {
    if (set) *setbits |= NWFA_CI;
    else *clearbits |= NWFA_CI;
  } else if (tool_strsame(p, "DI")) {
    if (set) *setbits |= NWFA_DI;
    else *clearbits |= NWFA_DI;
  } else if (tool_strsame(p, "RI")) {
    if (set) *setbits |= NWFA_RI;
    else *clearbits |= NWFA_RI;
  } else if (tool_strsame(p, "N") || tool_strsame(p, "NORMAL")) {
    *clearbits |= (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 (tool_strsame(p, "ALL")) {
    *setbits |= (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(uint32 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, "- ");
  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, uint32 attr)
{
  fprintf(stdout, "     %-23s  ", name);
  flag_print_attrs(attr);
  fprintf(stdout, "  \n");
}

static void flag_display_one_paged(char *name, uint32 attr,
                                   int *line_count, int *continuous)
{
  flag_display_one(name, attr);
  tool_page_line(line_count, continuous);
}


static char *flag_last_sep(char *s)
{
  char *last = NULL;
  char *p;

  for (p = s; *p; p++) {
    if (*p == '\\' || *p == '/' || *p == ':')
      last = p;
  }

  return(last);
}

static void flag_split_pattern(char *pattern, char *parent, int parent_size,
                               char *leaf, int leaf_size)
{
  char temp[260];
  char *sep;
  int plen;

  parent[0] = '\0';
  leaf[0] = '\0';

  strmaxcpy(temp, pattern, sizeof(temp) - 1);
  sep = flag_last_sep(temp);

  if (!sep) {
    strmaxcpy(leaf, temp, leaf_size - 1);
    return;
  }

  if (*(sep + 1)) {
    strmaxcpy(leaf, sep + 1, leaf_size - 1);
  } else {
    strmaxcpy(leaf, "*.*", leaf_size - 1);
  }

  plen = (int)(sep - temp);

  /*
   * Keep drive-root paths as "F:\" rather than "F:".
   */
  if (*sep == ':' && (*(sep + 1) == '\\' || *(sep + 1) == '/'))
    plen += 2;
  else if (*sep == ':' || *sep == '\\' || *sep == '/')
    plen += 1;

  if (plen >= parent_size)
    plen = parent_size - 1;

  memcpy(parent, temp, plen);
  parent[plen] = '\0';
}

static int flag_enter_parent(char *pattern, char *leaf, int leaf_size,
                             char *oldcwd, int oldcwd_size)
{
  char parent[260];

  oldcwd[0] = '\0';
  flag_split_pattern(pattern, parent, sizeof(parent), leaf, leaf_size);

  if (!leaf[0])
    strmaxcpy(leaf, "*.*", leaf_size - 1);

  if (!parent[0])
    return(0);

  if (!getcwd(oldcwd, oldcwd_size))
    oldcwd[0] = '\0';

  if (chdir(parent)) {
    if (oldcwd[0])
      chdir(oldcwd);
    return(-1);
  }

  return(0);
}

static void flag_leave_parent(char *oldcwd)
{
  if (oldcwd[0])
    chdir(oldcwd);
}


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

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

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

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

      flag_display_one_paged(ff.name, nwattrs, &line_count, &continuous);
      found++;
    }
  } while (!_dos_findnext(&ff));

  return(found);
}

static int flag_apply(char *pattern, uint32 setbits, uint32 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_obtain_attrs(fname, &attrs))
      attrs = (uint32)ff.attrib;

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

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

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

    flag_display_one(fname, newattrs);
    shown++;

  } while (!_dos_findnext(&ff));

  return(shown);
}

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

  (void)mode;

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

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

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

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

  {
    char leaf[260];
    char oldcwd[260];

    if (flag_enter_parent(path, leaf, sizeof(leaf), oldcwd, sizeof(oldcwd))) {
      flag_split_pattern(path, oldcwd, sizeof(oldcwd), leaf, sizeof(leaf));
      fprintf(stdout, "Files could not be found with pattern \"%s\"\a", leaf);
      return(1);
    }

    if (have_change) {
      rc = flag_apply(leaf, setbits, clearbits);
      flag_leave_parent(oldcwd);
      if (rc < 0) {
        fprintf(stdout, "Files could not be found with pattern \"%s\"\a", leaf);
        return(1);
      }

      return(0);
    }

    rc = flag_list(leaf);
    flag_leave_parent(oldcwd);
    if (rc < 0) {
      fprintf(stdout, "Files could not be found with pattern \"%s\"\a", leaf);
      return(1);
    }
  }

  return(0);
}
