Theme
The virtual file system object Preview
A virtual file system object is how a script touches storage. It works the same way whether the storage underneath is a local disk, an S3 bucket, an Azure container, a Google Cloud bucket or a remote SFTP server, and it deals with encryption at rest for you: encrypted storage decrypts on read and encrypts on write, transparently.
There are three ways to get one.
GetCurrentVFS()
js
function GetCurrentVFS(): VirtualFileSystem | null;Returns the live virtual file system the event happened in. This is the one you want almost every time, because it is already pointing at the right storage, already holds the right credentials, and already knows the at rest key.
js
var vfs = GetCurrentVFS();
if (vfs === null) {
Log.Warn("no virtual file system on this event");
Exit(0);
}
var st = vfs.Stat(CtxRelPath());
if (st.Ok()) {
Log.Info(CtxRelPath() + " is " + st.Info().Size + " bytes");
} else {
Log.Error("stat failed: " + st.ErrorMsg());
}The paths you pass it are relative to the virtual file system root, POSIX style, which is exactly what CtxRelPath() already gives you.
It returns null on session.disconnect and only there; every other event has a virtual file system. A null check costs nothing and is worth keeping.
GetCurrentVFS() ignores the user's permissions
The object it returns has full access to that entire virtual file system. It is not narrowed to the folders the triggering user can reach, and it does not apply their read, write or delete permissions.
That is by design: the Connector checks the user's permissions before it calls your script, so by the time the script runs the question has already been answered for the operation that triggered it. But a script is then free to read and write anywhere in that virtual file system, on behalf of a user who could not have done so themselves.
If a script reads a path derived from user input, a user can steer it somewhere you did not intend. Validate the paths you build:
js
// A user controlled name must never be allowed to climb out of the folder you meant.
var name = ExtractName(CtxRelPath());
if (name === "" || name.indexOf("..") >= 0) {
Log.Error("refusing suspicious name: " + CtxRelPath());
Exit(1);
}
var target = "/processed/" + name;Operations through GetCurrentVFS() are not bounded by the handler timeout
A call on this object runs without its own deadline. If the storage behind it stalls, for instance an S3 endpoint that stops answering, the call blocks until the storage gives up, and the handler timeout cannot interrupt it partway through a network read. On a before handler that is a stalled transfer for the user.
Keep before handler work on this object small: a Stat, a short read, a rename. Do bulk reads and writes on an after handler marked Run in the background. A virtual file system you build yourself with new VirtualFS(...) does carry the script deadline, so it does not have this behavior.
GetVFSByName()
js
function GetVFSByName(name: string): VirtualFileSystem;Returns one of the virtual file systems defined on this Connector, by the name you gave it in the Storage page, so a script can work across two of your storages without carrying any credentials. The classic use is a cross storage copy:
js
var archive = GetVFSByName("Archive");
var resp = GetCurrentVFS().CopyToVFS(CtxRelPath(), archive, "/incoming/" + ExtractName(CtxRelPath()));
if (!resp.Ok()) {
Log.Error("archive copy failed: " + resp.ErrorMsg());
}Names are matched exactly against the storage name first, then against its id. An unknown name fails the script the uncatchable way a failed new VirtualFS(...) does, and so does an ambiguous one (two storages sharing a name), with a message telling you to use the id instead.
Each script run gets its own fresh connection to the named storage, opened on first use, reused for the rest of the run, and closed when the run ends. Encryption at rest is handled with the same keys the Connector itself uses.
GetVFSByName() also ignores the user's permissions
Everything the danger block above says about GetCurrentVFS() applies here too, and more so: the returned object has full access to a storage the triggering user may have no access to at all. Scripts are written by Connector administrators and act with the administrator's reach; treat any path a user can influence with the same suspicion.
Like GetCurrentVFS(), operations on this object are not bounded by the handler timeout; the note above applies unchanged.
Creating a virtual file system object
js
new VirtualFS(vfsType, target, payload?)Builds a temporary virtual file system pointing wherever you like. It exists for the run of the script and is discarded afterwards. Unlike GetCurrentVFS(), its operations honor the handler timeout.
| Argument | Meaning |
|---|---|
vfsType | One of the type constants |
target | Where the storage is: a path, or a URL in the scheme for that type |
payload | Optional JSON string with credentials and options |
js
// A local directory on the Connector host.
var scratch = new VirtualFS(VfsTypeDisk, "/srv/scratch");
// The same directory, encrypted at rest with a passphrase.
var vault = new VirtualFS(VfsTypeDisk, "/srv/vault", '{"encryption_pass":"correct horse battery staple"}');Targets and payloads
| Type | target | payload keys |
|---|---|---|
VfsTypeDisk | A filesystem path: /srv/data, C:\\Data, \\\\server\\share | encryption_pass |
VfsTypeS3 | s3://bucket, s3://bucket/prefix?region=us-east-1, s3://bucket@endpoint/prefix?legacyPathStyle=true | access_key, access_secret, region, endpoint, legacy_path_style, insecure, upload_concurrency, download_concurrency, encryption_pass |
VfsTypeAzure | azblob://container, azblob://container/prefix, azblob://container@endpoint/prefix | account_name, account_key, container_name, endpoint, sas_token, insecure, upload_concurrency, download_concurrency, encryption_pass |
VfsTypeGCS | gs://bucket, gs://bucket/prefix | project_id, bucket_name, bucket_path, credentials_json, upload_concurrency, download_concurrency, encryption_pass |
VfsTypeSFTP | sftp://user@host:port/path | username, password, private_key, private_key_pass, max_retries, retry_interval |
The payload keys are snake_case, and a typo is silent
These JSON keys use snake_case, unlike most JSON in Syncplify software. A key that does not match, accessKey instead of access_key for example, is not an error: it is ignored, and the field keeps its default. The result is a virtual file system that fails to authenticate for no visible reason. Copy the names from the table above exactly.
Credentials never travel in the target URL; only in the payload. That is deliberate, because the target is what gets logged.
js
var bucket = new VirtualFS(
VfsTypeS3,
"s3://acme-archive/incoming?region=eu-west-1",
JSON.stringify({
access_key: accessKey,
access_secret: accessSecret,
region: "eu-west-1"
})
);A failed construction cannot be caught
If new VirtualFS(...) cannot build the virtual file system, for instance because the credentials are wrong or the path does not exist, the script stops immediately. A try/catch around it is not consulted; the error goes to the Connector log and the handler reports failure, which on a fail closed before handler refuses the operation.
Check what you can beforehand, with DirExists() or TcpConnect(), and keep constructions off before handlers where a failure costs a user their transfer.
VirtualFSByName is not available here
new VirtualFSByName("name") exists as a global, but on the Connector it always fails, and it fails the uncatchable way described above:
named VFS construction from configuration is not available in Connector scripts; use GetVFSByName(name) for a VFS defined on this ConnectorThat constructor is Syncplify Server's way of rebuilding a storage from stored configuration. On the Connector the same need is served by GetVFSByName(), which hands you a live object without ever exposing the stored credentials to script code.
R2FS is not available here either
VfsTypeR2FS is defined but cannot be constructed on the Connector: there is no R2FS client wired into the engine, so new VirtualFS(VfsTypeR2FS, ...) fails with R2FS support not available. The Connector is the other end of that protocol, not a client of it.
Reading a result
Every method returns a response object rather than a bare value, because JavaScript functions cannot return a value and an error together. Check Ok() first, always.
js
var resp = vfs.ReadDir("/incoming", SortByTime, SortDesc);
if (!resp.Ok()) {
Log.Error("could not list /incoming: " + resp.ErrorMsg());
Exit(1);
}
resp.Infos().forEach(function (item) {
Log.Info(item.Name + " " + item.Size + " " + item.Type);
});| Response type | Returned by | Extra method |
|---|---|---|
respBase | Everything that has no value to return | none |
respStat | Stat, Lstat | Info() returns one DirListItem |
respDir | ReadDir | Infos() returns an array of DirListItem |
respSize | TreeSize | Size() returns bytes |
respReadString | ReadFileAsText, ReadFileAsHex | Data() returns a string |
respReadBytes | ReadFileAsBytes | Data() returns an array of bytes |
Every one of them has these three, inherited from respBase:
js
resp.Ok(); // boolean: did it succeed
resp.ErrorMsg(); // string: human readable reason when it did not, "" when it did
resp.Error(); // the underlying error objectOk() and ErrorMsg(), not success and error
Some older Syncplify Server examples show resp.success and resp.error as properties. Those do not exist. They are methods, and they are called Ok() and ErrorMsg(). Reading resp.success yields undefined, which is falsy, so a check written that way treats every successful call as a failure.
The methods
The full list, with signatures and examples, is on VFS method reference.