/*
 * 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: FLAGDIR utility for displaying and changing NetWare directory attributes.
 * Depends on: net.h, ncpapi.h, netcall.c requester helpers, ncpapi.c namespace/NCP helpers, tools.c shared utility routines.
 */
/* flagdir.c - Novell FLAGDIR-like DOS utility, Client32 version */

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

#define FD_NWFA_H   0x00000002UL
#define FD_NWFA_SY  0x00000004UL
#define FD_NWFA_P   0x00010000UL
#define FD_NWFA_RI  0x00020000UL
#define FD_NWFA_DI  0x00040000UL

#define FD_DIR_BITS (FD_NWFA_H | FD_NWFA_SY | FD_NWFA_P | FD_NWFA_RI | FD_NWFA_DI)
#define FD_DM_ATTRIBUTES 0x00000002UL


#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 fd_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 = 0x16;            /* hidden/system/subdir */
  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 attributes at offset 8. */
  if (attrs)
    *attrs = tool_get_dword_lh(repl.data + 8);

  return(0);
}

static int fd_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 = 0x16;            /* hidden/system/subdir */
  U32_TO_BE32(0xffffffffUL, req.searchsequence);
  U32_TO_32(FD_DM_ATTRIBUTES, req.change_bits);
  tool_put_dword_lh(req.attributes, attrs);

  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 char *fd_last_sep(char *s)
{
  char *last = NULL;
  char *p;

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

  return(last);
}

static void fd_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 = fd_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);
  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 fd_enter_parent(char *pattern, char *leaf, int leaf_size,
                           char *oldcwd, int oldcwd_size)
{
  char parent[260];

  oldcwd[0] = '\0';
  fd_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 fd_leave_parent(char *oldcwd)
{
  if (oldcwd[0])
    chdir(oldcwd);
}

static int fd_current_display_path(uint8 dhandle, char *out, int max)
{
  char path[260];
  char *p;

  if (!out || max < 2)
    return(-1);

  out[0] = '\0';
  path[0] = '\0';

  if (get_dir_path(dhandle, path) || !path[0])
    return(-1);

  p = strchr(path, ':');
  if (p)
    p++;
  else
    p = path;

  if (!*p)
    out[0] = '\0';
  else
    strmaxcpy(out, p, max - 1);

  return(0);
}

static void fd_help(void)
{
  fprintf(stdout, "386 Usage:  Flagdir [path [option...]]\n");
  fprintf(stdout, "Options:  Normal\n");
  fprintf(stdout, "          System\n");
  fprintf(stdout, "          Hidden\n");
  fprintf(stdout, "          Deleteinhibit\n");
  fprintf(stdout, "          Purge\n");
  fprintf(stdout, "          Renameinhibit\n");
}

static int fd_attr_mask(char *s, uint32 *setbits, uint32 *clearbits)
{
  if (tool_strsame(s, "N") || tool_strsame(s, "NORMAL")) {
    *clearbits |= FD_DIR_BITS;
  } else if (tool_strsame(s, "S") || tool_strsame(s, "SY") ||
             tool_strsame(s, "SYS") || tool_strsame(s, "SYSTEM")) {
    *setbits |= FD_NWFA_SY;
  } else if (tool_strsame(s, "H") || tool_strsame(s, "HIDDEN")) {
    *setbits |= FD_NWFA_H;
  } else if (tool_strsame(s, "DI") || tool_strsame(s, "DELETEINHIBIT")) {
    *setbits |= FD_NWFA_DI;
  } else if (tool_strsame(s, "P") || tool_strsame(s, "PURGE")) {
    *setbits |= FD_NWFA_P;
  } else if (tool_strsame(s, "RI") || tool_strsame(s, "RENAMEINHIBIT")) {
    *setbits |= FD_NWFA_RI;
  } else if (tool_strsame(s, "PRIVATE") || tool_strsame(s, "PR")) {
    fprintf(stderr, "Private is valid on NetWare 2.15 and above, except NetWare 386.\n");
    return(-1);
  } else {
    fprintf(stderr, "Unknown attribute: %s.\n", s);
    return(-1);
  }
  return(0);
}

static void fd_print_attrs(uint32 attrs)
{
  int any = 0;

  if (!(attrs & FD_DIR_BITS)) {
    fprintf(stdout, "Normal \n");
    return;
  }

  if (attrs & FD_NWFA_SY) {
    fprintf(stdout, "System");
    any = 1;
  }
  if (attrs & FD_NWFA_H) {
    fprintf(stdout, "%sHidden", any ? " " : "");
    any = 1;
  }
  if (attrs & FD_NWFA_DI) {
    fprintf(stdout, "%sDeleteInhibit", any ? " " : "");
    any = 1;
  }
  if (attrs & FD_NWFA_P) {
    fprintf(stdout, "%sPurge", any ? " " : "");
    any = 1;
  }
  if (attrs & FD_NWFA_RI) {
    fprintf(stdout, "%sRenameInhibit", any ? " " : "");
    any = 1;
  }

  fprintf(stdout, " \n");
}

static void fd_display_header(char *path)
{
  char up[260];
  char prefix[260];
  char header[300];
  int len;
  char *p;

  if (tool_current_prefix(prefix, sizeof(prefix)))
    prefix[0] = '\0';

  tool_upcopy(up, path, sizeof(up));

  /* fd_process_one() enters the parent directory before displaying the
   * entry.  Use the current NetWare prefix plus the leaf/relative name,
   * not the original F:\... argument, otherwise the drive-qualified path is
   * appended to the already changed current directory.
   */
  strmaxcpy(header, prefix, sizeof(header) - 1);
  if (!tool_is_current_path(up) && up[0]) {
    len = strlen(header);
    if (len > 0 && header[len - 1] != ':' &&
        header[len - 1] != '\\' && header[len - 1] != '/') {
      if (len < (int)sizeof(header) - 1) {
        header[len++] = '\\';
        header[len] = '\0';
      }
    }
    if ((int)(strlen(header) + strlen(up)) < (int)sizeof(header) - 1)
      strcat(header, up);
  }

  /* Novell FLAGDIR displays SERVER/SYS: and uses / as path separator in
   * the header line.
   */
  for (p = header; *p; p++) {
    if (*p == '\\')
      *p = '/';
  }

  fprintf(stdout, "%s     \n", header);
}

static void fd_display_row(char *name, uint32 attrs)
{
  char base[260];

  tool_basename(base, name, sizeof(base));
  fprintf(stdout, "    %-12.12s    ", base);
  fd_print_attrs(attrs);
}

static void fd_display(char *path, uint32 attrs)
{
  fd_display_header(path);
  fd_display_row(path, attrs);
}

static int fd_is_directory(char *path)
{
  struct find_t ff;
  unsigned attr = _A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_SUBDIR | _A_ARCH;

  if (tool_is_current_path(path))
    return(1);

  if (_dos_findfirst(path, attr, &ff))
    return(0);

  return((ff.attrib & _A_SUBDIR) != 0);
}

static int fd_obtain(char *path, uint8 dhandle, uint32 *attrs)
{
  if (!fd_ncp22_1e_obtain_attrs(path, attrs))
    return(0);

  if (ncp87_06_obtain_rim_attributes(path, (uint16)dhandle,
                                      attrs, NULL, NULL, NULL) == 0)
    return(0);
  return(-1);
}

static int fd_modify(char *path, uint8 dhandle, uint32 attrs)
{
  if (!fd_ncp22_25_modify_attrs(path, attrs))
    return(0);

  if (ncp87_07_modify_dos_attributes(path, (uint16)dhandle, attrs,
                                      NULL, NULL, NULL) == 0)
    return(0);
  return(-1);
}

static int fd_process_one(char *path, char *display_path, uint8 dhandle,
                          uint32 setbits, uint32 clearbits,
                          int have_change, int show_header)
{
  char leaf[260];
  char oldcwd[260];
  char *ncp_path;
  char *check_path;
  uint32 attrs;
  uint32 newattrs;
  int rc = 0;

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

  if (!tool_is_current_path(path)) {
    if (fd_enter_parent(path, leaf, sizeof(leaf), oldcwd, sizeof(oldcwd))) {
      fprintf(stderr, "Directory %s not found.\n", path);
      return(1);
    }
    check_path = leaf;
    ncp_path = leaf;
  } else {
    check_path = path;
    ncp_path = "";
  }

  if (!fd_is_directory(check_path)) {
    fd_display_header(ncp_path[0] ? ncp_path : path);
    fprintf(stdout, "Directory %s not found.", ncp_path[0] ? ncp_path : path);
    fd_leave_parent(oldcwd);
    return(1);
  }

  if (fd_obtain(ncp_path, dhandle, &attrs)) {
    if (tool_is_current_path(path) && display_path && !display_path[0] &&
        !have_change) {
      attrs = 0;
    } else {
      fprintf(stderr, "Unable to get directory attributes.\n");
      fd_leave_parent(oldcwd);
      return(1);
    }
  }

  if (have_change) {
    newattrs = (attrs | setbits) & ~clearbits;
    if (newattrs != attrs) {
      if (fd_modify(ncp_path, dhandle, newattrs)) {
        fprintf(stderr, "Unable to change attributes.\n");
        fd_leave_parent(oldcwd);
        return(1);
      }
      attrs = newattrs;
      fd_obtain(ncp_path, dhandle, &attrs);
    }
  }

  if (show_header)
    fd_display(ncp_path[0] ? ncp_path : display_path, attrs);
  else
    fd_display_row(ncp_path[0] ? ncp_path : display_path, attrs);

  fd_leave_parent(oldcwd);
  return(rc);
}

static int fd_process_wild(char *spec, uint8 dhandle,
                           uint32 setbits, uint32 clearbits,
                           int have_change)
{
  struct find_t ff;
  char dir[260];
  char pat[260];
  char search[260];
  char full[260];
  int found = 0;
  int rc = 0;
  int lines = 0;
  int continuous = 0;
  unsigned attr = _A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_SUBDIR | _A_ARCH;

  tool_parent_pattern(dir, pat, spec, sizeof(dir), sizeof(pat));
  tool_join_path(search, dir, pat, sizeof(search));

  if (_dos_findfirst(search, attr, &ff) != 0) {
    fprintf(stderr, "Directory %s not found.\n", spec);
    return(1);
  }

  fd_display_header(spec);

  do {
    if ((ff.attrib & _A_SUBDIR) && strcmp(ff.name, ".") &&
        strcmp(ff.name, "..")) {
      tool_join_path(full, dir, ff.name, sizeof(full));
      if (fd_process_one(full, ff.name, dhandle, setbits, clearbits,
                         have_change, 0))
        rc = 1;
      found++;
      tool_page_line(&lines, &continuous);
    }
  } while (_dos_findnext(&ff) == 0);

  if (!found) {
    fprintf(stderr, "Directory %s not found.\n", spec);
    return(1);
  }

  return(rc);
}

int func_flagdir(int argc, char *argv[], int mode)
{
  char *path = ".";
  char display_path[260];
  uint8 connid = 0;
  uint8 dhandle = 0;
  uint32 setbits = 0;
  uint32 clearbits = 0;
  int have_change = 0;
  int i;

  (void)mode;

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

  if (argc > 1)
    path = argv[1];

  if (tool_current_dhandle(&connid, &dhandle)) {
    fprintf(stderr, "FlagDir only works on network directories.\n");
    return(1);
  }

  for (i = 2; i < argc; i++) {
    if (fd_attr_mask(argv[i], &setbits, &clearbits))
      return(1);
    have_change = 1;
  }

  if (tool_has_wildcards(path))
    return(fd_process_wild(path, dhandle, setbits, clearbits, have_change));

  if (tool_is_current_path(path)) {
    if (fd_current_display_path(dhandle, display_path, sizeof(display_path)))
      strcpy(display_path, ".");
  } else {
    strmaxcpy(display_path, path, sizeof(display_path) - 1);
  }

  return(fd_process_one(path, display_path, dhandle,
                        setbits, clearbits, have_change, 1));
}
