CHANGES
-----------------------

1.0pre1
- Protocol v7 is now the minimum version. All clients and servers should
  be upgraded. Improved documentation.

0.9r3
- SECRET/K allows to specify a binary blob for the secret up to 1024 bytes
- Added BLAKE2-MAC authentication and SPECK64 stream encryption.
- Amiga server: Skips redundant Seeks, lazy read buffer allocation
- server: writing amiga.flags or amiga.comment xattrs on a file that has
  no owner-write permission (e.g. FIBF_WRITE set) failed silently with
  EACCES. Server now temporarily adds S_IWUSR before the xattr loop and
  restores the original mode afterwards.
- amiga-client: when do_rpc() hit a socket error and conn_dead() managed
  to reconnect, the triggering RPC still returned -EIO, which surfaced as
  a "not a DOS disk" requester. do_rpc() now retries the full RPC once
  after a successful reconnect, making server restarts transparent.
- added CRC32 option and CTRL-C to amiga-comparetree tool.
- Server should now work on MacOSX. Minor fixes in docs and messages

0.9
- Fizz default port changed to 17711 (from 7777)
- Amiga: bsdsocket.library version requirement relaxed to v3 (untested),
  code refactoring and cleanup, server and client free of globals. 
- protocol v6: dynamic payload negotiation. OP_WRITE ACK extended to
  written(4) max_payload(4); clients track max_write and chunk writes
  accordingly. get_max_payload(conn_t *) stub on both servers controls the
  limit for both reads and writes. Clients start at FIZZ_V6_INIT_MAX_WRITE
  (16KiB) and adjust from ACKs. v6 is a hard requirement: servers reject
  pre-v6 clients at cap exchange with a descriptive message= key; v6
  clients still degrade gracefully when connecting to older servers.
- get_max_payload() now also caps OP_READ response size server-side (both
  servers). Reads silently return fewer bytes than requested when the limit
  is below the client's maxsize; clients already handle partial reads with
  follow-up requests. get_max_payload() is now the single knob controlling
  memory exposure for both transfer directions.
- server rejection at cap exchange now includes a message= key with a
  human-readable reason. New clients print it verbatim; old clients fall
  back to their existing generic message. Version mismatch and cap-need
  failure produce distinct messages. Accepted connections log proto vN
  alongside the negotiated caps.
- IFACE got a [:port] option, error is raised if PORT and IFACE mismatch.
- Amiga server and client using memory pools.
- Amiga DEVNAME argument renamed to VOLNAME.
- Amiga server: child processes inherit parent's stack size; should be fine,
  our stack is pretty lean, only bsdsocket is the unknown
- server, fizz-mount: Unix CLI argument parsing rewritten with argparse
  (Amiga ReadArgs-style templates). Interface structure is now equivalent
  on both platforms. FUSE pass-through options captured via FUSEARGS/F.
- fizz (POSIX shell dispatcher): "query HOST[:PORT]" subcommand added,
  dispatches to fizz-mount with a dummy mountpoint and QUERY keyword.
  "version" subcommand prints version from fizz-serve. Clean usage
  shown on no-argument invocation; "query" with missing host gives a
  specific error. Version string sourced from binary, not hardcoded.
- fizz-mount, amiga-client: cap negotiation rejection now reports the
  failing constraint.
- fizz query / cmd_query now print the negotiated capability set in addition
  to the server's offer.
- reconnect failure suppresses the generic "giving up" message when the
  failure is a cap rejection (message already printed by try_connect).
- capability model simplified: deny= removed from the protocol and all
  clients/servers (redundant with need= for any real use case, and
  semantically ambiguous as a connection gate with implicit server-side
  redirects). nocase (strncasecmp-based, locale-dependent) removed from the
  Unix server offer. New capability amiga-case: deterministic Latin-1/ASCII
  case folding; latin1_lower() and latin1_ncasecmp() added to protocol.h.
  Unix server now offers case and amiga-case only. Amiga server now offers
  amiga-case only (nocase was redundant there; AmigaOS does the matching).
  Amiga client defaults to use=amiga-case. Clients support use= and need= only.
- Amiga D (Deletable) bit, which is a server extended attribute, should now
  be honoured in the Amiga client.
- assuming no v1/v2 clients are left in the field, pruned some code from
  clients that refers to pre-v3 protocol 
- clients, server: more verbose with regard to their offered capabilities
  and those negotiated when clients connect
- Amiga client: improved shutdown path, now reports held locks, tells the
  user to close applications and put away left out icons on the share
- capability management refactored and streamlined into a unified parser
- amiga-client: LOCKING: the prefix conflict rule (exclusive lock on a
  directory blocked all access to entries inside it) was wrong for AmigaDOS
  semantics. CreateDir() returns an exclusive lock; holding it while copying
  files into the new directory triggered ERROR_OBJECT_IN_USE for every file
  open, breaking "copy all clone". Removed the rule; exact and descendant
  checks remain.
- amiga-comparetree: comment_eq treated fib_Comment as a BCPL string (length
  byte at [0]) but dos.library converts it to a C string before returning from
  ExNext(), same as fib_FileName. Fixed to use strlen/memcmp from byte 0.
- amiga-server: mysnprintf (kprintf.asm) returned strlen+1 instead of strlen
  (the null character was counted). safe_path used the return value with a
  `>= outsz` overflow check, which fired when a non-rootcolon path (e.g.
  "SYS:T/filename") exactly filled the output buffer -- one byte tighter than
  a volume-root path because of the extra "/" separator. Result: OP_STAT
  returned EACCES for every file in a shared subdirectory while readdir still
  worked (the root path "/" never fills the buffer). Fixed by adding
  `subq.l #1,d0` in kprintf.asm so mysnprintf returns strlen, matching C99
  snprintf semantics.
- server: amiga-comment and amiga-flags capability added to the Unix server.
  Comment stored as xattr "amiga.comment" (raw bytes, max 79), flags as
  "amiga.flags" (1 byte, same bit layout as the wire protocol). Platform
  wrappers cover Linux (lgetxattr/lsetxattr/lremovexattr, "user." prefix
  added internally), macOS (getxattr/setxattr/removexattr with XATTR_NOFOLLOW,
  same "user." prefix), FreeBSD (extattr_get_link/extattr_set_link/
  extattr_delete_link with EXTATTR_NAMESPACE_USER, bare name); other
  platforms compile with silent no-ops. h_setxattr handler added; h_stat
  and h_readdir append xattr blocks when the client has negotiated the caps.
  Removing a comment or zeroing flags removes the xattr from the filesystem.
  At startup the server probes the root directory with a get of a non-existent
  attribute; if the filesystem returns ENOTSUP/EOPNOTSUPP the xattr caps are
  silently omitted from the offer so clients never negotiate them.
- server: new -v / --verbose flag; logs each xattr get, set, and remove
  operation to stderr with path, key, return value, and errno.
- amiga-comparetree: new standalone Amiga CLI test tool. Takes DIRA/A DIRB/A
  QUIET/S; walks both trees recursively and compares type, size, DateStamp,
  protection bits, and fib_Comment for every entry. Reports entries present in
  only one tree. Returns RETURN_OK (identical), RETURN_WARN (differences
  found), or RETURN_ERROR (I/O error). Useful for verifying round-trip copies
  via fizz.
- protocol v5: amiga-comment and amiga-flags capabilities. When both sides
  negotiate amiga-comment, STAT and READDIR responses append an xattr block
  after the fixed fields. Format: count(1) [klen(1) key(klen) vlen(1)
  val(vlen)]*count. Key "amiga.comment" carries fib_Comment as raw bytes
  (max 79). Key "amiga.flags" carries SPAD+H protection bits as one byte,
  active-high: bit0=delete-protected, bit1=archived, bit2=pure,
  bit3=script, bit4=hold. OP_SETXATTR (opcode 16) writes xattr entries:
  xattr_block_len(2) xattr_block path(remaining) -> empty.
  Amiga server offers amiga-comment and amiga-flags. Amiga client defaults
  to use=iso-8859-1,nocase,amiga-comment,amiga-flags. ACTION_SET_COMMENT
  is now forwarded to the server via OP_SETXATTR. amiga-comment and
  amiga-flags are silently inactive against servers that do not offer them.
- protocol v4: capability exchange. Wire: caplen(2,BE) text(caplen),
  newline-delimited key=value pairs (offer=, need=, use=, deny=,
  next=start/disconnect). Client sends first; server responds. Server
  rejects if any cap in client need= is absent from its offer=.
  Capability names: unix (Unix/POSIX server; real POSIX permission bits
  including owner/group/other), amiga (AmigaOS server; synthesised
  permissions, inherently case-insensitive), iso-8859-1 (Latin-1
  filenames), case (case-sensitive path matching), nocase (case-insensitive
  path matching). Unix server offers unix,case,nocase and implements
  nocase path resolution when requested (nocase_resolve, resolve_last=0
  for create/mkdir). Amiga server offers amiga,iso-8859-1,nocase.
  Clients: USE/NEED/DENY args (Amiga) and --use/--need/--deny options
  (FUSE); default use=iso-8859-1,nocase,amiga-comment,amiga-flags on
  Amiga client, use=unix,case on FUSE client (applied when none of
  use/need/deny are specified).
  Backward compat: FIZZ_PROTO_MIN_VERSION=3; v4/v5 clients connect to v3
  servers without cap exchange, falling back to case-sensitive mode.
- "fizz query HOST[:PORT]" (Amiga) and "fizz-mount --query HOST[:PORT]"
  (FUSE): connect, print server's protocol version and offered
  capabilities, then exit without mounting.
- amiga-client: VOLUME_ONLY #ifdef (commented out by default) skips the
  DLT_DEVICE entry and registers only a DLT_VOLUME, matching ch_nfsmount
  behaviour. Device name deduplication now applies to both explicit and
  default names; sequence is FIZZ, FIZZ2, FIZZ3, ... (not FIZZ1).
- updated systemd service file

0.8
- Unix Signal handling: SIGINT (first) gracefully stops accepting, waits
  for active clients; SIGHUP: same as first SIGINT; SIGTERM forcedly
  exits immediately regardless of active clients; SIGINT (second) same
  as SIGTERM. A self-pipe (g_wakefd) lets the forced path wake the
  pthread_cond_timedwait in the shutdown loop immediately rather than
  waiting up to 5 seconds for the next poll cycle. The wakeup_thread
  reads one byte from the pipe and signals the condvar (all signal
  handlers are async-signal-safe (write to a pipe is POSIX safe))
- added systemv and systemd init scripts and configs in etc/
- extended test suite
- some reformatting, Amiga client refactored to use no globals,
  smaller binary despite extended functionality
- Amiga client dirbuf (per NetLock) no longer capped to a constant, but
  to a fraction of the available memory. (currently this is merely for
  directory traversal, buffer goes away with its dirlock).
- protocol v3 now supports OP_STATFS; amiga client: some heuristic to
  scale blocksize and blocks on large drives into somewhat useful range
- SO_KEEPALIVE added to servers
- Amiga client: Added experimental LOCKING option, to enforce full Amiga
  locking semantics locally, per client instance. No locking goes over
  the wire protocol and so isn't enforced between remote clients yet.
- amiga-client: NetLock.path and OpenFile.path are now heap-allocated
  (trailing bytes of the same AllocVec block as the struct) instead of
  fixed char[512] arrays. Removes the 512-byte path cap and eliminates
  the wasted space for short paths. A global PATHBUF_SIZE (4096) scratch
  buffer (g_pathbuf, heap-allocated at mount time) replaces all per-call
  stack path buffers. Request payload buffers sized to the actual path
  length instead of the former fixed MAX_PATH upper bound.
- amiga-server, server: per-connection file handle table is now a
  heap-allocated array starting at 8 slots, doubled on exhaustion.
  Removes the hard MAX_FH=64 cap; the OS file descriptor limit becomes
  the effective ceiling, with EMFILE returned when that is reached.
- amiga-server: per-handler path buffers (path + safe_path output) are
  now heap-allocated to the actual payload length instead of fixed
  char[512] stack arrays. Removes the 512-byte path cap from all server
  ops (stat, readdir, open, create, mkdir, unlink, rename, truncate,
  chmod, setmtime).

0.7
- protocol: version bumped to 2. Clients now accept server_version >=
  FIZZ_PROTO_VERSION instead of requiring an exact match. This allows
  future additive versions without breaking already-deployed clients
- protocol: new OP_CHMOD (opcode 13). Wire format: mode(4) path -> empty.
- all: partial permission mapping between Unix mode bits and AmigaDOS
  fib_Protection bits. Owner r/w/x are mapped via FIBF_READ/WRITE/EXECUTE
  (all active-low). FIBF_DELETE is always left clear (deletable); group
  and other Unix bits are not mapped. Details in DESIGN.
- amiga-server: STAT and READDIR now derive mode from fib_Protection
  instead of always sending hardcoded 0755/0644.
- amiga-client: fill_fib() now converts received Unix mode to fib_Protection
  so protection bits are visible in directory listings and Protect command.
- fizz-mount, amiga-client: nfs_chmod()/ACTION_SET_PROTECT now forward to
  the server via OP_CHMOD. Silently accepted (no-op) against v1 servers.
- amiga-client: build_path() fixed for AmigaDOS parent-navigation: a
  leading '/' in a name is resolved as one level up per slash. Fixes
  "cd /", "/foo" paths, and "cd //" (grandparent) in shell and programs.
- amiga-client: build_path() treats a lone '.' component as current
  directory (no-op) so "cd ." works instead of returning an error.
- amiga-server: SetFileSize() returns the new file size on success (not
  DOSTRUE), so the failure check must be < 0, not !result. Using !result
  treated a successful truncate-to-zero (return 0) as an error. Fixed in
  both h_open (NF_TRUNC path) and h_truncate. This caused overwrite and
  trunc_zero to fail when the Amiga server was used as the backend.
- all: new OP_SETMTIME (opcode 14). Wire: mtime_sec(8) path -> empty.
  POSIX server uses utimensat() (atime unchanged). Amiga server uses
  SetFileDate(). FUSE client wires nfs_utimens() (was a no-op). Amiga
  client implements ACTION_SET_DATE via OP_SETMTIME; silently succeeds
  against v1 servers. Timestamps go through the same TZOFFS conversion
  as STAT/READDIR, so copy tools that preserve dates will round-trip
  correctly.
- amiga-client: ACTION_FLUSH now drains the async write pipeline and
  propagates any deferred write error, instead of silently succeeding.
- amiga-client: ACTION_INHIBIT now returns DOSFALSE/ERROR_ACTION_NOT_KNOWN
  instead of DOSTRUE, so callers know the volume cannot be inhibited.
- amiga-server, amiga-client: new TZOFFS/N argument (seconds, signed).
  AmigaDOS datestamps are local time; the protocol carries UTC. Without
  TZOFFS, file timestamps appear off by the TZ+DST difference. Set to
  the same value on both sides (e.g. TZOFFS 7200 for CEST). POSIX
  server and FUSE client are not affected (they work in UTC throughout).
- amiga-client: ACTION_FINDINPUT now requests NF_RDWR from the server
  instead of NF_RDONLY. AmigaOS FFS does not enforce open mode per
  filehandle; software that writes to a FINDINPUT handle (e.g. backup
  tools that re-open an archive to patch the header) worked against the
  Amiga server but failed against the POSIX server with a deferred EBADF
  surfacing at ACTION_END time.

0.6
- FreeBSD build fixes for stricter POSIX_SOURCE, FUSE_CAP_ATOMIC_0_TRUNC 
  not supported (our nfs_truncate implements it anyway)
- unix-client, amiga-client: writes are fire-and-forget; the ACK is
  collected asynchronously while the next write is already in flight.
  Eliminates one full round-trip latency per write on sequential writes.
  flush/fsync drain all in-flight writes and return any deferred error.
- amiga-client: TCP_NODELAY and SO_RCVBUF=256KB set on the socket.
  Default receive buffer (4-8 KB on most stacks) caused repeated TCP
  window stalls when receiving large read responses over fast links.
  Big read throughput gain on A3000/68060 over 100 Mbit/s Ethernet.
- amiga-client: ACTION_EXAMINE_OBJECT on the root lock now returns the
  volume name in fib_FileName. Previously returned empty string, causing
  applications that use NameFromLock to fail to open files on the mount.
- amiga-client: async write pipeline was unbounded; server ACKs (12 bytes
  each) accumulated in the TCP receive buffer without ever being drained
  during pure-write workloads (drain_async_writes is only reached via
  do_rpc, which is not called on a sequential write stream). Once the
  buffer filled the server blocked sending the next ACK, stopped reading
  new data, and the client blocked in send - permanent deadlock at a
  deterministic byte offset. Fixed by capping g_async_inflight at
  ASYNC_WRITE_MAX (4); drain_async_writes is called before sending when
  the limit is reached.
- amiga-client: device name passed to "fizz mount" is now stripped of a
  trailing colon so "DH0:" and "DH0" both work.
- server: h_readdir now silently skips entries whose names contain
  protocol-invalid characters (':', '\', control bytes) or are '.'/'..'.
  Such names appeared in POSIX directory listings as '?????????' because
  getattr rejected them via safe_path(); on the Amiga they appeared
  correctly but were unopenable. Now they are simply not served.
- fizz-mount: --omithidden flag hides dot-files from directory listings
  while keeping them fully accessible by path.
- amiga-client: OMITHIDDEN/S argument to "fizz mount" does the same.
- fizz-mount: automatic reconnect after server disconnect. Retries every
  5 s for up to 60 s; ops block during reconnect and resume transparently.
  Open file handles return EBADF (stale server fh) after reconnect.
  If all retries fail, ops return EIO as before.
- amiga-client: same reconnect behaviour, handled inline in conn_dead()
  using Delay(); the current DOS packet fails with an error, subsequent
  ones resume normally after a successful reconnect.
- fizz-serve, amiga-server: client disconnect messages now always printed
  (previously only during shutdown), including the client IP address.
- fizz-serve, amiga-server: new --interface/INTERFACE option to bind to a
  specific IP address instead of all interfaces. Binding to 127.0.0.1
  restricts access to localhost; binding to an internal NIC address limits
  exposure on multi-homed hosts.
