LinkedIn Article Draft — Architecture Section

Designing It So Almost Nothing Changed

One of the first questions was architectural: how do you add network awareness to a 20+ year old C++ codebase without turning it inside out?

pwsafe has a very clean separation between its core logic, its UI, and the operating-system layer that actually opens and closes files. After reading through the code, one detail stood out: there were only two real entry points to the filesystem — functions called FOpen and FClose.

That was the hinge. If I could intercept those two calls, I wouldn’t need to rewrite the core, the crypto layer, or the UI. The rest of the application could stay blissfully unaware that anything had changed.

So instead of ‘adding WebDAV everywhere’, we added a small transport layer that sits underneath those two functions. If the path looks like a normal local file, everything behaves exactly as before. If the path looks like a URL, it’s handed off to a dynamically loaded plugin — a small .so file that knows how to fetch and store data over a particular protocol.

The beauty of that approach is that it keeps the change set conceptually small. The core still thinks it’s opening a local file. In reality, the plugin downloads the database to a secure local cache, lets pwsafe work on it as usual, and then pushes changes back to the server on save.

Architecturally, that decision shaped everything that followed. Instead of a ‘WebDAV feature’, we built a transport plugin system. WebDAV was just the first implementation. In theory, the same mechanism could support S3, SFTP, or anything else in the future.

Why Minimal Change Mattered

Working in a mature open-source project teaches you humility. The existing code works. It’s been tested by thousands of users over many years. The fastest way to introduce subtle, security-sensitive bugs is to start modifying core logic you don’t fully understand.

By confining the new behaviour to the operating-system abstraction layer, we reduced the blast radius. The crypto layer remained untouched. The database format didn’t change. The command pattern and undo/redo system were unaffected. From the perspective of the core, it was still just reading and writing bytes.

That restraint wasn’t accidental. It was a design principle we returned to repeatedly: if we can solve this at the edge, don’t touch the heart.

The Hardest Problem: Locking Across Crashes

Fetching and storing files over HTTPS is straightforward with a library like libcurl. The real complexity was locking.

WebDAV supports server-side locks. When you open a database for editing, you acquire a lock token. As long as you hold that token, other clients can’t overwrite the file. When you’re done, you release it.

That sounds simple — until you think about crashes.

What happens if the application is closed abruptly? What if it’s killed by the operating system? What if a signal handler runs while parts of the runtime are already being torn down?

In the original design, pwsafe used a local sidecar file (.plk) to coordinate locking. That works fine on a local filesystem. It does not work reliably over WebDAV. We needed something stronger: a guarantee that the server-side lock would always be released — even on abnormal exit.

The solution was unexpectedly old-school Unix: fork a child process.

On the first lock acquisition, the application spawns a small ‘lock daemon’ process connected by a Unix socket. That child process owns the WebDAV lock token. The parent — the main GUI application — never talks to the server directly about locks. It sends tiny binary messages to the child, and the child performs the network operations.

Why go to that trouble? Because certain system calls (like write) are safe to use inside signal handlers. Complex network libraries are not. By isolating the lock management in a separate process, we could guarantee that if the main application crashed or was killed, the operating system would close the socket. The child would detect EOF and immediately release any locks before exiting.

In other words, the safety guarantee doesn’t rely on the application behaving well. It relies on the kernel cleaning up file descriptors. That’s a much stronger foundation.

Ironically, this tiny background process — only a few hundred lines of code — became the hardest part of the entire project. Getting inter-process communication right. Avoiding injection vulnerabilities. Handling edge cases like file descriptor inheritance. Thinking through every shutdown path.

It was a reminder that in systems programming, the hardest problems are rarely about the happy path. They’re about what happens when everything goes wrong.