Documentation
¶
Overview ¶
Package writegate is the one question every person-facing write asks before it touches a storage: may this path be changed?
Two rules, one call, so that a door cannot honour one and forget the other:
- filex's own names (syspath): `.filex-trash`, `.versions`, `.thumbs`, the desktop's `.filex-open` and the `.keepdir` marker are never written by a person — except the desktop's open-with round trip, which the caller claims with a syspath.Verb (syspath.Refused).
- app locks: while an app holds a lock on a path (the signing app freezes a document while signatures are collected, `files:lock`), nothing changes it, moves it or removes the folder around it — whoever asks, administrators included — except the app that holds the lock.
⚠⚠ Why one package (2026-09-21). The lock check lived in five handlers (the explorer's verbs, the queue, the text editor, two upload paths) and nowhere else. Measured before this package: the document server's save callback overwrote a frozen document ({"error":0}); an agent's delete of the folder holding it answered {"ok":true}; an archive extracted over it; a WebDAV DELETE of its folder answered 204; FTP and SFTP sessions opened before the freeze overwrote and renamed it; NFS renamed it. The signing app's own hash check caught the change later — the request then died as "the document changed", and the freeze the person was promised on screen ("… dondurulmuş; imzalar toplanırken kimse değiştiremez") was never real. The reserved-name rule had just been written into the same doors, one call each; the lock rule now rides in that call.
A third rule rides in it since the vault (encryption level 3, docs/E2E-VAULT-FORMAT.md → Writes from anywhere else): inside a vault folder only the vault API writes. Its packs and index files are written whole, once, under a write lock the server keeps; a file dropped into `v/`, a pack renamed or deleted from WebDAV, an archive extracted into the vault would be writes the vault's index knows nothing about - at best litter, at worst a pack an index still names, gone. Every door asks Check already, so every door refuses them: the explorer's file API, the queue, uploads, the agent API and MCP, ShareX, tickets, file requests, archives, apps, the document server, WebDAV, SFTP, FTPS, NFS and S3. The vault folder ITSELF stays an ordinary encrypted folder - renamed, moved, deleted whole or copied - and its key file is rewritten through its usual door (a password change, an escrow slot), which claims RewritesKeyFile and checks what it writes.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrLocked = errors.New("locked by an app")
ErrLocked is what a refused write wraps when an app holds a lock.
var ErrVaultKeyFile = errors.New("a vault's key file keeps its v, req and vault, and is not removed or moved on its own")
ErrVaultKeyFile is what a refused write wraps when it would change a vault's key file without the door's RewritesKeyFile claim - or when a door's check of the new bytes failed (e2e.SameVaultBlock).
var ErrVaultPath = errors.New("inside a vault, only the vault API writes")
ErrVaultPath is what a refused write wraps when it touches something strictly inside a vault folder: only the vault API writes there.
Functions ¶
func Check ¶
Check reports whether a write that names targets may happen: nil, a *syspath.ReservedError, a *VaultPathError or a *LockedError. Reserved names are judged first and without a database: a name that can never be written is refused the same way whether or not anything is locked. The vault rule comes next, when locks can answer it (Vaults), then the app locks.
app is the app plugin doing the write — 0 for a person, a protocol client or a document server. A lock held by that same app does not stop it: the app that froze the document is the one that must still write into it (the signed output), exactly the waiver acl.Set.EffectiveIgnoringLocks gives its jobs.
locks may be nil (no ACL resolver wired): then only names are judged.
func RefusesMounted ¶
RefusesMounted is Check for a protocol client (WebDAV, SFTP, FTPS, NFS, S3): filex's own directories (syspath.Mounted — the keep marker is an ordinary file there) and the storage's live locks.
⚠ Pass locks read NOW (acl.Resolver.Locks), not a session's cached permission set: SFTP, FTPS and NFS load that set once when the session starts, so it does not see a freeze taken afterwards. Measured before this (2026-09-21): a session opened before the signing app froze imza/NDA.docx overwrote it, renamed it and renamed the folder holding it.
Types ¶
type LockedError ¶
type LockedError struct {
Lock *model.AppPluginLock
Rel string
}
LockedError names the lock that refused a write and the path it was asked about, so the answer can say who froze it and why (handlers.lockedAnswer).
func (*LockedError) Error ¶
func (e *LockedError) Error() string
func (*LockedError) Unwrap ¶
func (e *LockedError) Unwrap() error
type Locks ¶
type Locks interface {
Lock(rel string) *model.AppPluginLock
LockWithin(rel string) *model.AppPluginLock
}
Locks answers which app lock covers a path: on it (Lock) or on it or anything under it (LockWithin). *acl.Set implements it; a nil *acl.Set answers "none".
type Target ¶
type Target struct {
Rel string
// Verb is the syspath exception this target may claim (the desktop's
// open-with round trip, a person's own draft); syspath.Change — the zero
// value — for every other write.
Verb syspath.Verb
// Person is who the write is FOR, when the exception depends on it
// (syspath.OwnDraft: a draft is written only by its owner's editor). 0
// when nobody is named, which is what every other write passes.
Person int64
// contains filtered or unexported fields
}
Target is one path a write names.
func Names ¶
Names is a path the write names without changing it: the folder a file is created, moved or extracted INTO, a source that is only read (a copy, an archive being packed or unpacked), the target of a share or a grant. Judged on reserved names only — a lock freezes a document and the folder entry that holds it, not what may be added beside it.
func Writes ¶
Writes is a path the write changes: its content is created or replaced, or the entry is created, moved or removed — and, for a folder, everything under it goes along. Judged on reserved names AND on app locks on the path or anywhere below it.
func (Target) By ¶ added in v0.48.0
By names the person the write is for (syspath.RefusedBy): the owner of a draft is the only one whose OwnDraft claim is honoured.
func (Target) ForVault ¶ added in v0.54.0
ForVault claims the vault API's exception: the target is a pack, an index file, the temporary index or the vault's own folder and key file, written by POST/PUT /api/files/e2e/vault/* under its write lock. Filex's own names and app locks are judged as for every write. No other door claims it.
func (Target) RewritesKeyFile ¶ added in v0.54.0
RewritesKeyFile claims that the write replaces a vault's key file with new bytes the door has checked keep its `v`, `req` and `vault` (e2e. SameVaultBlock) - the password change, the reset with the recovery key, an escrow slot added or declined, which the browser writes through the explorer's upload. Without the claim a vault's key file is never written, renamed, moved or deleted on its own (ErrVaultKeyFile).
type VaultPathError ¶ added in v0.54.0
type VaultPathError struct {
Root string
Rel string
// KeyFile: the path is the vault's key file (ErrVaultKeyFile).
KeyFile bool
}
VaultPathError names the vault folder and the path that refused a write.
func (*VaultPathError) Error ¶ added in v0.54.0
func (e *VaultPathError) Error() string
func (*VaultPathError) Unwrap ¶ added in v0.54.0
func (e *VaultPathError) Unwrap() error