/*
 * 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: CREATOR utility for showing and changing NetWare file creator/owner metadata.
 * Depends on: net.h, ncpapi.h, netcall.c requester helpers, ncpapi.c namespace/NCP helpers, tools.c shared utility routines.
 */
/* creator.c - friendly creator/modifier/archiver metadata test tool
 *
 * Uses the same NCP 22/25 Set Directory/File Information path as Novell
 * FILER for the special fileinfo/archive metadata fields.  This is intended
 * for testing mars_nwe xattr-backed metadata without using the FILER GUI.
 */

#include "net.h"
#include "ncpapi.h"

#define DM_CREATE_DATE       0x00000004UL
#define DM_CREATE_TIME       0x00000008UL
#define DM_CREATOR_ID        0x00000010UL
#define DM_ARCHIVE_DATE      0x00000020UL
#define DM_ARCHIVE_TIME      0x00000040UL
#define DM_ARCHIVER_ID       0x00000080UL
#define DM_MODIFY_DATE       0x00000100UL
#define DM_MODIFY_TIME       0x00000200UL
#define DM_MODIFIER_ID       0x00000400UL

#define BINDERY_USER         0x0001
#define BINDERY_GROUP        0x0002

#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

#define CREATOR_ATTR_RENAME_INHIBIT 0x00020000UL
#define CREATOR_ATTR_DELETE_INHIBIT 0x00040000UL

#define CREATOR_RIGHT_S 0x01
#define CREATOR_RIGHT_R 0x02
#define CREATOR_RIGHT_W 0x04
#define CREATOR_RIGHT_C 0x08
#define CREATOR_RIGHT_E 0x10
#define CREATOR_RIGHT_M 0x20
#define CREATOR_RIGHT_F 0x40
#define CREATOR_RIGHT_A 0x80

static void creator_usage(void)
{
  fprintf(stdout, "Usage:\n");
  fprintf(stdout, "  CREATOR file /SHOW\n");
  fprintf(stdout, "  CREATOR file /CREATOR  user-or-hexid\n");
  fprintf(stdout, "  CREATOR file /MODIFIER user-or-hexid\n");
  fprintf(stdout, "  CREATOR file /ARCHIVER user-or-hexid\n");
  fprintf(stdout, "  CREATOR file /CREATE   user-or-hexid [yyyy-mm-dd [hh:mm:ss]]\n");
  fprintf(stdout, "  CREATOR file /MODIFY   user-or-hexid [yyyy-mm-dd [hh:mm:ss]]\n");
  fprintf(stdout, "  CREATOR file /ARCHIVE  user-or-hexid [yyyy-mm-dd [hh:mm:ss]]\n");
  fprintf(stdout, "  CREATOR file /ALL      user-or-hexid [yyyy-mm-dd [hh:mm:ss]]\n");
  fprintf(stdout, "\n");
  fprintf(stdout, "Examples:\n");
  fprintf(stdout, "  CREATOR F:\\CIXTEST\\SUP\\S_SUP.TXT /SHOW\n");
  fprintf(stdout, "  CREATOR F:\\CIXTEST\\SUP\\S_SUP.TXT /CREATOR MARIO\n");
  fprintf(stdout, "  CREATOR F:\\CIXTEST\\SUP\\S_SUP.TXT /ARCHIVE MARIO\n");
}

static int hex_value(int c)
{
  if (c >= '0' && c <= '9') return(c - '0');
  if (c >= 'a' && c <= 'f') return(c - 'a' + 10);
  if (c >= 'A' && c <= 'F') return(c - 'A' + 10);
  return(-1);
}

static int parse_hex32(char *s, uint32 *out)
{
  uint32 value = 0;
  int i;
  int v;

  if (!s || !*s || !out)
    return(-1);
  if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
    s += 2;
    if (!*s) return(-1);
  }
  for (i = 0; s[i]; i++) {
    v = hex_value(s[i]);
    if (v < 0) return(-1);
    value = (value << 4) | (uint32)v;
  }
  *out = value;
  return(0);
}

static uint32 lookup_object_id(char *name)
{
  uint32 id;
  uint8 namebuf[48];

  if (!name || !*name)
    return(0);

  if (!parse_hex32(name, &id))
    return(id);

  strmaxcpy(namebuf, name, sizeof(namebuf) - 1);
  upstr(namebuf);
  id = ncp17_35_get_bindery_object_id(namebuf, BINDERY_USER);
  if (id)
    return(id);
  id = ncp17_35_get_bindery_object_id(namebuf, BINDERY_GROUP);
  return(id);
}

static int parse_uint(char *s, int *out)
{
  int v = 0;
  int i;

  if (!s || !*s || !out)
    return(-1);
  for (i = 0; s[i]; i++) {
    if (s[i] < '0' || s[i] > '9') return(-1);
    v = v * 10 + (s[i] - '0');
  }
  *out = v;
  return(0);
}

static uint16 make_dos_date(int year, int month, int day)
{
  if (year < 80)
    year += 2000;
  if (year < 1980)
    year = 1980;
  return((uint16)(((year - 1980) << 9) | (month << 5) | day));
}

static uint16 make_dos_time(int hour, int min, int sec)
{
  return((uint16)((hour << 11) | (min << 5) | (sec / 2)));
}

static void current_dos_datetime(uint16 *date_out, uint16 *time_out)
{
  REGS regs;
  int year;
  int month;
  int day;
  int hour;
  int min;
  int sec;

  regs.h.ah = 0x2a;
  int86(0x21, &regs, &regs);
  year = regs.x.cx;
  month = regs.h.dh;
  day = regs.h.dl;

  regs.h.ah = 0x2c;
  int86(0x21, &regs, &regs);
  hour = regs.h.ch;
  min = regs.h.cl;
  sec = regs.h.dh;

  if (date_out) *date_out = make_dos_date(year, month, day);
  if (time_out) *time_out = make_dos_time(hour, min, sec);
}

static int parse_date_arg(char *s, uint16 *date_out)
{
  char buf[32];
  char *p1;
  char *p2;
  int y;
  int m;
  int d;
  uint32 hex;

  if (!s || !date_out)
    return(-1);
  if (!parse_hex32(s, &hex) && (s[0] == '0' || strlen(s) <= 4)) {
    *date_out = (uint16)hex;
    return(0);
  }

  strmaxcpy(buf, s, sizeof(buf)-1);
  p1 = strchr(buf, '-');
  if (!p1) p1 = strchr(buf, '.');
  if (!p1) return(-1);
  *p1++ = '\0';
  p2 = strchr(p1, '-');
  if (!p2) p2 = strchr(p1, '.');
  if (!p2) return(-1);
  *p2++ = '\0';

  if (parse_uint(buf, &y) || parse_uint(p1, &m) || parse_uint(p2, &d))
    return(-1);

  /* Support both yyyy-mm-dd and dd.mm.yy. */
  if (y < 100 && d > 100) {
    int tmp = y;
    y = d;
    d = tmp;
  }
  if (y < 100) y += 2000;
  if (m < 1 || m > 12 || d < 1 || d > 31)
    return(-1);

  *date_out = make_dos_date(y, m, d);
  return(0);
}

static int parse_time_arg(char *s, uint16 *time_out)
{
  char buf[32];
  char *p1;
  char *p2;
  int h;
  int m;
  int sec = 0;
  uint32 hex;

  if (!s || !time_out)
    return(-1);
  if (!parse_hex32(s, &hex) && (s[0] == '0' || strlen(s) <= 4)) {
    *time_out = (uint16)hex;
    return(0);
  }

  strmaxcpy(buf, s, sizeof(buf)-1);
  p1 = strchr(buf, ':');
  if (!p1) return(-1);
  *p1++ = '\0';
  p2 = strchr(p1, ':');
  if (p2) *p2++ = '\0';

  if (parse_uint(buf, &h) || parse_uint(p1, &m))
    return(-1);
  if (p2 && parse_uint(p2, &sec))
    return(-1);
  if (h < 0 || h > 23 || m < 0 || m > 59 || sec < 0 || sec > 59)
    return(-1);

  *time_out = make_dos_time(h, m, sec);
  return(0);
}

static void print_dos_date(uint16 date)
{
  int year = ((date >> 9) & 0x7f) + 1980;
  int month = (date >> 5) & 0x0f;
  int day = date & 0x1f;
  fprintf(stdout, "%04d-%02d-%02d", year, month, day);
}

static void print_dos_time(uint16 time)
{
  int hour = (time >> 11) & 0x1f;
  int min = (time >> 5) & 0x3f;
  int sec = (time & 0x1f) * 2;
  fprintf(stdout, "%02d:%02d:%02d", hour, min, sec);
}

static int split_path(char *path, char *parent, int parent_size, char *name, int name_size)
{
  char tmp[260];
  char *p;

  if (!path || !*path || !parent || !name)
    return(-1);

  strmaxcpy(tmp, path, sizeof(tmp)-1);
  korrpath(tmp);
  p = strrchr(tmp, '\\');
  if (!p) p = strrchr(tmp, '/');

  if (p) {
    *p++ = '\0';
    strmaxcpy(parent, tmp, parent_size-1);
    strmaxcpy(name, p, name_size-1);
  } else {
    parent[0] = '\0';
    strmaxcpy(name, tmp, name_size-1);
  }

  if (!name[0]) return(-1);
  return(0);
}

static int set_info(uint8 dhandle, char *name, uint32 bits,
                    uint16 cdate, uint16 ctime, uint32 cid,
                    uint16 adate, uint16 atime, uint32 aid,
                    uint16 mdate, uint16 mtime, uint32 mid)
{
  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  created_time[2];
    uint8  created_date[2];
    uint8  created_id[4];
    uint8  archived_time[2];
    uint8  archived_date[2];
    uint8  archived_id[4];
    uint8  updated_time[2];
    uint8  updated_date[2];
    uint8  updated_id[4];
    uint8  rest[80];
  } req;
  struct { uint16 len; } repl;

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

  req.func = 0x25;
  req.dirhandle = dhandle;
  req.search_attributes = 0x06;
  U32_TO_BE32(0xffffffffUL, req.searchsequence);
  U32_TO_32(bits, req.change_bits);

  U16_TO_16(ctime, req.created_time);
  U16_TO_16(cdate, req.created_date);
  U32_TO_BE32(cid, req.created_id);
  U16_TO_16(atime, req.archived_time);
  U16_TO_16(adate, req.archived_date);
  U32_TO_BE32(aid, req.archived_id);
  U16_TO_16(mtime, req.updated_time);
  U16_TO_16(mdate, req.updated_date);
  U32_TO_BE32(mid, req.updated_id);

  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(-neterrno);
  return(0);
}


static void creator_attr_string(uint32 attr, char *out)
{
  out[0] = '[';
  out[1] = 'R';
  out[2] = (attr & _A_RDONLY) ? 'o' : 'w';
  out[3] = '-';
  out[4] = (attr & _A_ARCH) ? 'A' : '-';
  out[5] = '-';
  out[6] = (attr & _A_HIDDEN) ? 'H' : '-';
  if (attr & _A_SYSTEM) {
    out[7] = 'S';
    out[8] = 'y';
  } else {
    out[7] = '-';
    out[8] = '-';
  }
  memset(out + 9, '-', 8);
  out[17] = (attr & CREATOR_ATTR_DELETE_INHIBIT) ? 'D' : '-';
  out[18] = (attr & CREATOR_ATTR_RENAME_INHIBIT) ? 'R' : '-';
  out[19] = ']';
  out[20] = '\0';
}

static void creator_rights_string(uint16 rights, char *out)
{
  uint8 old = (uint8)rights;

  out[0] = '[';
  out[1] = (old & CREATOR_RIGHT_S) ? 'S' : '-';
  out[2] = (old & CREATOR_RIGHT_R) ? 'R' : '-';
  out[3] = (old & CREATOR_RIGHT_W) ? 'W' : '-';
  out[4] = (old & CREATOR_RIGHT_C) ? 'C' : '-';
  out[5] = (old & CREATOR_RIGHT_E) ? 'E' : '-';
  out[6] = (old & CREATOR_RIGHT_M) ? 'M' : '-';
  out[7] = (old & CREATOR_RIGHT_F) ? 'F' : '-';
  out[8] = (old & CREATOR_RIGHT_A) ? 'A' : '-';
  out[9] = ']';
  out[10] = '\0';
}

static int show_info(char *name, uint8 dhandle)
{
  NCP_NDIR_INFO info;
  int rc;
  char attr_text[24];
  char rights_text[12];

  memset(&info, 0, sizeof(info));
  rc = ncp87_06_obtain_ndir_info(tool_is_current_path(name) ? "" : name,
                                  (uint16)dhandle,
                                  &info,
                                  NULL, NULL, NULL);
  if (rc) {
    fprintf(stdout, "CREATOR: obtain info failed for %s, rc=%d\n", name, rc);
    return(rc);
  }

  creator_attr_string(info.attributes, attr_text);
  creator_rights_string(info.inherited_rights, rights_text);

  fprintf(stdout, "File: %s\n", name);
  fprintf(stdout, "Attributes: %s (0x%08lX)\n", attr_text, (unsigned long)info.attributes);
  fprintf(stdout, "Create:   "); print_dos_date(info.creation_date);
  fprintf(stdout, " "); print_dos_time(info.creation_time);
  fprintf(stdout, " creator=0x%08lX\n", (unsigned long)info.creator_id);
  fprintf(stdout, "Modify:   "); print_dos_date(info.modify_date);
  fprintf(stdout, " "); print_dos_time(info.modify_time);
  fprintf(stdout, " modifier=0x%08lX\n", (unsigned long)info.modifier_id);
  fprintf(stdout, "Archive:  "); print_dos_date(info.archive_date);
  fprintf(stdout, " "); print_dos_time(info.archive_time);
  fprintf(stdout, " archiver=0x%08lX\n", (unsigned long)info.archiver_id);
  fprintf(stdout, "Access date: "); print_dos_date(info.last_access_date);
  fprintf(stdout, "\nRights mask: %s (0x%04X)\n", rights_text, (unsigned)info.inherited_rights);
  return(0);
}

int func_creator(int argc, char *argv[], int mode)
{
  char oldcwd[260];
  char parent[260];
  char name[64];
  char *path;
  char *cmd;
  char *id_arg;
  uint8 dhandle = 0;
  uint32 id = 0;
  uint16 date;
  uint16 time;
  uint32 bits = 0;
  int rc = 0;
  int need_id = 0;

  (void)mode;

  if (argc < 3) {
    creator_usage();
    return(1);
  }

  path = argv[1];
  cmd = argv[2];
  upstr(cmd);

  if (split_path(path, parent, sizeof(parent), name, sizeof(name))) {
    fprintf(stdout, "CREATOR: bad path %s\n", path);
    return(1);
  }

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

  if (parent[0] && chdir(parent)) {
    fprintf(stdout, "CREATOR: cannot change to parent %s\n", parent);
    return(1);
  }

  if (tool_current_dhandle_only(&dhandle)) {
    fprintf(stdout, "CREATOR: current drive is not a network drive\n");
    if (oldcwd[0]) chdir(oldcwd);
    return(1);
  }

  if (!strcmp(cmd, "/SHOW") || !strcmp(cmd, "SHOW")) {
    rc = show_info(name, dhandle);
    if (oldcwd[0]) chdir(oldcwd);
    return(rc);
  }

  need_id = 1;
  if (argc < 4) {
    creator_usage();
    if (oldcwd[0]) chdir(oldcwd);
    return(1);
  }
  id_arg = argv[3];
  id = lookup_object_id(id_arg);
  if (!id) {
    fprintf(stdout, "CREATOR: object not found: %s\n", id_arg);
    if (oldcwd[0]) chdir(oldcwd);
    return(1);
  }

  current_dos_datetime(&date, &time);
  if (argc >= 5 && parse_date_arg(argv[4], &date)) {
    fprintf(stdout, "CREATOR: bad date '%s'\n", argv[4]);
    if (oldcwd[0]) chdir(oldcwd);
    return(1);
  }
  if (argc >= 6 && parse_time_arg(argv[5], &time)) {
    fprintf(stdout, "CREATOR: bad time '%s'\n", argv[5]);
    if (oldcwd[0]) chdir(oldcwd);
    return(1);
  }

  if (!strcmp(cmd, "/CREATOR") || !strcmp(cmd, "CREATOR")) {
    bits = DM_CREATOR_ID;
    rc = set_info(dhandle, name, bits, 0, 0, id, 0, 0, 0, 0, 0, 0);
  } else if (!strcmp(cmd, "/MODIFIER") || !strcmp(cmd, "MODIFIER")) {
    bits = DM_MODIFIER_ID;
    rc = set_info(dhandle, name, bits, 0, 0, 0, 0, 0, 0, 0, 0, id);
  } else if (!strcmp(cmd, "/ARCHIVER") || !strcmp(cmd, "ARCHIVER")) {
    bits = DM_ARCHIVER_ID;
    rc = set_info(dhandle, name, bits, 0, 0, 0, 0, 0, id, 0, 0, 0);
  } else if (!strcmp(cmd, "/CREATE") || !strcmp(cmd, "CREATE")) {
    bits = DM_CREATE_DATE | DM_CREATE_TIME | DM_CREATOR_ID;
    rc = set_info(dhandle, name, bits, date, time, id, 0, 0, 0, 0, 0, 0);
  } else if (!strcmp(cmd, "/MODIFY") || !strcmp(cmd, "MODIFY")) {
    bits = DM_MODIFY_DATE | DM_MODIFY_TIME | DM_MODIFIER_ID;
    rc = set_info(dhandle, name, bits, 0, 0, 0, 0, 0, 0, date, time, id);
  } else if (!strcmp(cmd, "/ARCHIVE") || !strcmp(cmd, "ARCHIVE")) {
    bits = DM_ARCHIVE_DATE | DM_ARCHIVE_TIME | DM_ARCHIVER_ID;
    rc = set_info(dhandle, name, bits, 0, 0, 0, date, time, id, 0, 0, 0);
  } else if (!strcmp(cmd, "/ALL") || !strcmp(cmd, "ALL")) {
    bits = DM_CREATE_DATE | DM_CREATE_TIME | DM_CREATOR_ID |
           DM_ARCHIVE_DATE | DM_ARCHIVE_TIME | DM_ARCHIVER_ID |
           DM_MODIFY_DATE | DM_MODIFY_TIME | DM_MODIFIER_ID;
    rc = set_info(dhandle, name, bits, date, time, id, date, time, id, date, time, id);
  } else {
    creator_usage();
    if (oldcwd[0]) chdir(oldcwd);
    return(1);
  }

  if (rc) {
    fprintf(stdout, "CREATOR: NCP22/25 set info failed for %s, rc=%d neterrno=0x%02X\n",
            path, rc, (unsigned)(rc < 0 ? -rc : 0));
  } else {
    fprintf(stdout, "CREATOR: set %s on %s to 0x%08lX OK\n",
            cmd, path, (unsigned long)id);
    if (bits & (DM_CREATE_DATE | DM_MODIFY_DATE | DM_ARCHIVE_DATE)) {
      fprintf(stdout, "CREATOR: date/time "); print_dos_date(date);
      fprintf(stdout, " "); print_dos_time(time); fprintf(stdout, "\n");
    }
  }

  if (!rc)
    show_info(name, dhandle);

  if (oldcwd[0]) chdir(oldcwd);
  return(rc ? rc : 0);
}
