| name | smbutils |
| description | Use when inspecting, validating, repairing, importing into, or maintaining a Synchronet Message Base (SMB) file — mail (mail.shd), message sub-boards (data/subs/<code>.shd), or file bases (data/file/<code>.shd) — via the smbutil, chksmb, or fixsmb command-line tools. Trigger whenever the user mentions smbutil, chksmb, fixsmb, SMB files, .shd / .sdt / .sid / .sha files, "the mail base", "rebuild/renumber the message base", "pack the sub", "check the message base for corruption", "repair an SMB", "dump a hash file", "view headers", "import a message", or any low-level inspection, validation, or repair of a Synchronet messaging file that isn't doable from inside the BBS or MsgBase JS API. Prefer this skill over guessing flags or shelling out blind. |
Synchronet SMB Utilities — smbutil, chksmb, fixsmb
Three command-line tools work on Synchronet Message Base (SMB) files. They
overlap in scope but each has a specific job:
smbutil — the Swiss-army knife. List/read/view messages, import,
pack, run maintenance, lock, light repair (re-init headers).
chksmb — read-only integrity checker. Walks the base and reports
corruption. Run this first when something looks wrong.
fixsmb — targeted repair: rebuild the index, undelete, renumber,
rehash. Use after chksmb tells you what's broken.
SMB is the common on-disk format for:
- Local email (
data/mail.shd)
- Message sub-boards (
data/subs/<subcode>.shd)
- File bases (
data/file/<dircode>.shd)
If the task can be done from inside Synchronet (SCFG, the BBS itself, or a
MsgBase JS script via jsexec), prefer that. Reach for smbutil when:
- The BBS is offline / a base is corrupt and JS can't open it
- You need a quick listing/dump that doesn't justify a script
- You're importing FidoNet / netmail / e-mail from a text file
- You're packing/repairing a base from cron or a shell wrapper
Locating the install
The path conventions in this skill are relative to the Synchronet install
root. Use the $SBBSCTRL environment variable to find a live install
— it points to the ctrl/ subdirectory, and the install root is its
parent. So on any properly-configured host:
SBBS="$(dirname "$SBBSCTRL")"
"<sbbs>/exec/smbutil" ...
"<sbbs>/data/mail"
"<sbbs>/docs/smb.html"
Recipes below use shorthand: bare command names (smbutil, chksmb,
fixsmb) and install-relative paths (exec/..., data/..., docs/...).
On a host where <sbbs>/exec isn't on PATH, prepend "<sbbs>/" or cd "<sbbs>" first.
The .exe variants of each binary in exec/ are for Windows — don't run
them on Linux. Conversely on Windows (incl. Git Bash / MSYS / WSL talking
to a Windows install), you must call the .exe explicitly — ./smbutil will
attempt to exec the Linux ELF and fail with Exec format error. Use
./exec/smbutil.exe (or cd <sbbs> && exec/smbutil.exe ...). Also note
$SBBSCTRL on Windows is a Windows-style path like C:\sbbs\ctrl (or
whichever drive the install lives on); from Git Bash / MSYS the same path
is reachable via the /c/sbbs/ctrl mount-point form.
SMB format reference
Before doing anything non-trivial — interpreting a chksmb error, deciding
whether -renumber is safe, writing a script that reads SMB directly —
consult the format spec. Knowing what each on-disk file does demystifies
most of these tools' commands.
Quick file-role cheat-sheet (the per-base on-disk set):
| File | Role |
|---|
.shd | Status + headers — base-level status block followed by per-message header records. The "main" file. |
.sdt | Data — message bodies and binary attachment payloads. |
.sid | Index — per-message index entries for fast lookup by index/number. |
.sha | Hash file — content/from/to/subject hashes for dedupe and lookup. (Filename .hash is also seen on some bases — same role.) |
.sda | Data allocation map (Fast/Hyper allocation modes). |
.ini | Per-base configuration — max messages, age limits, attribute defaults, etc. |
Filespec convention (all three tools)
Each tool takes one or more SMB base paths. The path is the base name
without the per-file extension — the tool opens the full file set
(.shd, .sdt, .sid, .sha, .sda, .ini). Passing the .shd
extension explicitly also works; it's stripped.
data/mail # local e-mail
data/subs/syncdata.synchronet # sub-board
data/file/<dircode> # file base
All three tools accept one or more bases — shell globs (e.g. *.shd) work
because each tool iterates over the expanded list. For example,
smbutil m data/subs/*.shd runs maintenance on every sub.
smbutil
Invocation form
smbutil [opts] <cmd>[<cmd>...] <filespec>[.shd] [<filespec> ...]
<cmd> is a single letter (case-sensitive — D is destructive, d is
reversible). Some commands accept an immediate suffix (e.g. l10, r#42,
v-7).
Commands can be concatenated. smbutil mp data/subs/*.shd runs
maintenance (m) and then pack (p) across every sub. The corruption-repair
recipe smbutil -C Rcs <base> chains re-init, change-status, and show-status
in one invocation.
The mail base is auto-maintained by the Synchronet Terminal Server (a
nightly internal pass). You don't normally need to run smbutil m on
data/mail — reach for it only when investigating corruption or running a
manual import.
Examples:
smbutil s data/mail
smbutil l data/subs/syncdata.synchronet
smbutil V10 data/mail
smbutil v#1234 data/mail
smbutil v-30 data/mail
Commands
Group by what you actually want to do:
Inspect / read
| Cmd | What it does |
|---|
s | Show message base status (totals, header info) — start here for any base |
l[n] | List msgs/files starting at index n |
v[n] | View msg headers from index n |
V[n] | Verbose view (all header fields) |
r[n] | Read full msgs (header + body) from index n |
x[n] | Dump the raw msg index (.sid) |
h | Dump the hash file (.sha) — useful for duplicate-detection debugging |
[n] accepts three forms:
n — 1-based index offset into the base
#n — actual message number (the persistent ID stored in the header)
-n — age in days (e.g. v-7 = messages from the last week)
Use a bare -<N> option to limit how many messages a l/v/V/r
prints (e.g. -1 r#42 → read just message #42 and stop; -10 l → list
ten). This is a plain negative-number option, not -#<N> — -# is
rejected as Unknown opt '#'. (The --help line -# = set number of messages uses # only as a stand-in for the number.)
Import
| Cmd | What it does |
|---|
i[file] | Import a generic message (RFC822-ish text); omit file to read stdin |
e[file] | Import as e-mail (mail base) |
n[file] | Import as netmail |
These pair with the -t, -n, -u, -f, -e, -s options to set
to/from/subject when the input doesn't already have headers. Use -d to skip
the prompts.
Maintain / pack / repair
| Cmd | What it does |
|---|
m | Run base maintenance — purge expired and over-max messages per .ini rules |
p[k] | Pack the base (reclaim space). k = minimum reclaimable Kbytes to bother |
-a | (option) Always pack — skip the compression-worthwhile check |
R | Re-initialize / repair SMB and status headers — use after corruption |
L | Lock the base for exclusive access (backup window) |
U | Unlock |
c | Change msg base status fields interactively |
Z | Hold an SMB header lock until keypress (lock-testing) |
Destructive
| Cmd | What it does |
|---|
d | Flag all messages for deletion (reversible with u until next pack) |
u | Unflag — clears the delete flag on all messages |
D | Delete and remove all messages immediately — not reversible |
Confirm with the user before running D, R, or p -a on a live base.
For R, copy the base files aside first — re-init touches header structure.
Verbose-dump (V) field names
smbutil V[n] prints the full per-message header. The fields aren't
documented in --help; the ones you'll actually filter on:
| Field | What it is |
|---|
index record | 1-based index offset of this message in the base |
Subject | Decoded subject line |
RFC822From | Original From: header (raw, before SBBS rewrites) |
Sender / SenderNetType / SenderNetAddr | SBBS-canonical sender (name, type, address) |
SenderTime | UTC timestamp the sender stamped, format YYYYMMDDHHMMSSZ |
SenderIpAddr / SenderHostName / SenderProtocol | Connection metadata |
To | Recipient address (name or email) |
ToExt | Recipient user number — 1 is the sysop, others are the user .dat index |
ToList | Original RFC822 To/Cc list |
Records are separated by a blank line in V output, which makes them awk-friendly.
"Show all mail for one user" recipe (smbutil-only)
smbutil doesn't take a "filter by recipient" flag, but you can awk over
verbose output. To list every message in data/mail addressed to user #1:
smbutil V data/mail | awk '
/^index record/ { idx=$3; subj=""; from=""; date=""; toext=""; next }
/^Subject / { sub(/^Subject *[ \t]+/,""); subj=$0; next }
/^RFC822From / { sub(/^RFC822From *[ \t]+/,""); from=$0; next }
/^Sender / { if (from=="") { sub(/^Sender *[ \t]+/,""); from=$0 } next }
/^SenderTime / { sub(/^SenderTime *[ \t]+/,""); date=$0; next }
/^ToExt / { sub(/^ToExt *[ \t]+/,""); toext=$0; next }
/^$/ { if (toext=="1") printf "%-15s [#%-6s] %.45s :: %s\n", date, idx, from, subj
idx=""; subj=""; from=""; date=""; toext="" }'
Swap V for V-30 to limit to the last 30 days, or V-7 for a week.
For very busy mail bases (~6k+ messages) prefer the windowed forms — full
V produces megabytes of output. The official answer for repeated /
filtered reads of one user's mail is still a MsgBase JS script via
jsexec; this awk recipe is the one-liner equivalent.
[n] and count-limit pitfalls
- To limit the message count, use a bare
-<N> option, not -#<N>.
-#N is rejected (Unknown opt '#') — the -# in --help is just a
placeholder for the number. So smbutil -1 r#60063 <base> reads exactly
one message (#60063) and stops; smbutil -10 l <base> lists ten. The
count option is independent of the [n] starting-position suffix and
combines with it.
- You can also bound by age (
V-7/V-30) or start partway through
(V<startIndex>) instead of, or in addition to, a count limit.
- The starting index
[n] form is a 1-based offset into the base, not
a message number. Use #n if you have the persistent message ID.
r with no count limit reads from the start position to the end of
the base and paginates, so when piped / run non-interactively it can
stall or appear to hang (or get auto-backgrounded) waiting for a keypress.
To read one specific message cleanly in a script:
smbutil -o -1 r#<num> <base> </dev/null — -1 caps it at one message,
</dev/null satisfies the pager, -o puts any error on stdout so it's
captured in the same stream.
Frequently useful options
| Opt | Effect |
|---|
-c[m] | Create the base if it doesn't exist (m = max msgs) |
-d | Use defaults for to/from/subject prompts (good for scripted imports) |
-l | LZH-compress imported message text |
-i | Ignore dupes — don't write CRCs, don't check hash file |
-r | Show raw body bytes (skip MIME decoding) — debugging only |
-v | Increase console verbosity (repeatable) |
-o | Send errors to stdout instead of stderr (easier to pipe/log) |
-<N> | Limit number of messages printed by l/v/V/r (e.g. -1 = one). Not -#<N>, which is rejected |
-C | Continue past normally-fatal errors |
-z<tz> | Set timezone offset for the imported message timestamp |
-t / -n / -u / -f / -e / -s | to-name / to-addr / to-user# / from-name / from-user# / subject |
Common recipes
Inspect mail base health:
smbutil s data/mail
View the last week of mail with full headers:
smbutil V-7 data/mail
Find a specific message by number and dump its body:
smbutil -1 r#12345 data/mail </dev/null
(Without -1, r keeps reading to the end of the base and paginates.)
Repair a corrupt sub-board (after backing it up):
cp data/subs/syncdata.synchronet.* /tmp/smb-backup/
smbutil R data/subs/syncdata.synchronet
smbutil s data/subs/syncdata.synchronet
Severely corrupted base (fixsmb can't open it for index repair):
smbutil -C Rcs data/subs/badbase
-C continues past normally-fatal errors; R re-inits SMB/status headers,
c lets you reconfigure status values (max msgs, CRCs, age), s prints
the resulting status. After this, run chksmb, then fixsmb for any
remaining errors.
Pack a sub to reclaim space (only if >1MB recoverable):
smbutil p1024 data/subs/syncdata.synchronet
Run scheduled maintenance (drop expired + over-max messages):
smbutil m data/subs/syncdata.synchronet
Import a netmail message from a text file:
smbutil -t"SysOp" -s"Hello" n/tmp/msg.txt data/mail
For automated/scripted posting, the official docs recommend the
postmsg.js module over smbutil i — it understands sub-board codes,
applies the usual posting hooks, and is friendlier to maintain:
jsexec postmsg.js <subcode> "<from>" "<to>" "<subject>" </tmp/body.txt
(See the jsexec skill for details.)
chksmb
chksmb walks an SMB base and reports problems — it's read-only. It
does not modify anything. Use it first when you suspect corruption,
when something fails to open from the BBS, or as a periodic health check.
Invocation form
chksmb [-opts] <filespec>[.shd] [<filespec> ...]
Multiple bases can be checked in one run. Exit code is non-zero if errors
were found, so it's cron-friendly.
Options
| Opt | Effect |
|---|
-b | Beep on error |
-s | Stop after the first errored base (useful when checking many) |
-p | Pause after each errored base |
-h | Skip the hash file check |
-a | Skip the allocation file check |
-t | Skip translation-string checks |
-i | Skip message-ID checks |
-S | Skip subject-CRC checks |
-N | Skip to/from name-CRC checks |
-e | Display extended info on each corrupted message |
The skip flags exist for cases where one file is so damaged that checking
it spams output and masks problems elsewhere — skip it and come back after
running fixsmb on that piece.
Recipes
Check the mail base:
chksmb data/mail
Check every sub-board, stop on first error, show extended info:
chksmb -se data/subs/*.shd
Cron-style nightly health check (exit code drives alerting):
chksmb data/mail data/subs/*.shd || echo "SMB ERRORS" | mail sysop
fixsmb
fixsmb performs targeted repair on an SMB base. Unlike smbutil R
(which re-initializes header structure), each fixsmb flag fixes one
specific kind of problem. Use it after chksmb tells you what's wrong.
Invocation form
fixsmb [-renumber] [-undelete] [-fixnums] [-rehash] <smb_file> [<smb_file> ...]
Accepts multiple bases. The base name follows the same filespec rules.
Repair flags
| Flag | What it does |
|---|
-renumber | Rebuild the index (.sid), assigning fresh sequential numbers. Use with caution — message numbers are persistent identifiers; renumbering breaks any outside reference (FidoNet seen-by, archived URLs, user pointers) that mentions the old number. Prefer -fixnums when you only need to reconcile mismatches. |
-undelete | Clear the "deleted" flag on every message in the base. Recovery option — pairs with an accidental smbutil d or a botched maintenance run, as long as the base hasn't been packed yet. |
-fixnums | Reconcile the per-message numbers stored in headers with the index. Use when chksmb reports mismatched message numbers but the index is otherwise sane. |
-rehash | Rebuild the hash file (.sha) from current message content. Use when chksmb -h reports hash mismatches, or after manual edits to message bodies. |
With no flags, fixsmb performs a generic index rebuild — equivalent in
spirit to -renumber plus integrity passes. Pass specific flags when you
know what you're targeting.
Caution
fixsmb writes to the base. Always back up first:
cp data/subs/syncdata.synchronet.* /tmp/smb-backup/
fixsmb -renumber data/subs/syncdata.synchronet
chksmb data/subs/syncdata.synchronet
If the base is in active use, lock it first (smbutil L), repair, then
unlock (smbutil U). Better: stop the BBS, or at least the relevant
service, before repair.
Investigating a corrupt base — full workflow
When something looks wrong (e.g. a sub fails to open, mail counts look
off, chksmb was triggered by cron):
- Identify which base, from logs or the user's report.
- Back up the full file set:
cp data/subs/<code>.* /tmp/smb-backup/
- Run
chksmb -e to see exactly what's broken.
- Pick the right
fixsmb flag based on what chksmb reported:
- "msg index out of order" →
-renumber
- "hash mismatch" →
-rehash
- "msg number doesn't match index" →
-fixnums
- Messages disappeared but base wasn't packed →
-undelete
- Re-run
chksmb to confirm clean.
smbutil s to verify status totals look right.
smbutil p if there's reclaimable space after repair.
For end-of-life cleanup or scheduled trimming, prefer smbutil m
(maintenance honors the .ini retention rules) over manual deletes.
When not to use these tools
- Bulk read/edit across many messages → write a JS script using
MsgBase
(see the jsexec skill). Iterating with
MsgBase.get_all_msg_headers() is faster and more flexible.
- Reading a single user's mail →
MsgBase in JS, filtering by
to_number / to_ext.
- Anything reachable through SCFG → use SCFG. These utilities bypass
configuration validation.
Cross-references
Official wiki pages — consult these first for current option lists,
edge-case behavior, and any changes since this skill was written:
Also:
- SMB on-disk format spec: https://wiki.synchro.net/ref:smb (and the
brief cheat-sheet earlier in this file).
MsgBase JS API (preferred for programmatic work): see the
jsexec skill, and the stock JS modules in exec/ of the
Synchronet install for usage examples.
- Source code (authoritative when this skill, wiki, and help text disagree):
https://gitlab.synchro.net/main/sbbs — see
src/sbbs3/smbutil.c,
src/sbbs3/chksmb.c, src/sbbs3/fixsmb.c, and the src/smblib/
reference library.