Hard times with hard links
I wanted to have the same file in multiple places in my Obsidian vault. A symlink is the natural way to do this, but the Obsidian desktop app does not recognize symlinks.1
So I turned to the other kind of filesystem link on Unix-like operating systems: hard links.
A symlink is a reference to a path, while a hard link is a reference to an inode, the filesystem data structure that stores a file's metadata and its location on disk. Symlinks and hard links behave the same in most respects, but not when the target file is deleted: the symlink will be left dangling, while the hard link will keep the file's contents alive. The filesystem keeps a count of links to an inode and will not delete the file contents while there is at least one link. In fact, a regular file is just a hard link with a link count of 1 (or, a hard link is just a regular file with a link count greater than 1), while symlinks are a distinct file type alongside regular files and directories.
Suppose I have a hard link journal.md that points to the same inode as archive/2026/2026-journal.md. Any edits that I make to journal.md will also apply to archive/2026/2026-journal.md. If I delete journal.md, the archive file will remain untouched. So far, so good.
Now, what happens if a program overwrites journal.md?
- If the program opens it with
O_TRUNCand writes out the new contents, thenjournal.mdandarchive/2026/2026-journal.mdwill continue to share the same inode. - If the program instead creates a temporary file, writes to it, and atomically replaces
journal.mdusingrename(2),journal.mdwill now have a different inode fromarchive/2026/2026-journal.md, and the two files will be decoupled: any future changes I make tojournal.mdwill not affect the archive file. The hard link is broken – the two files are still valid, but they are no longer linked to each other as was intended.
To avoid breaking the link, you could check the file's link count before overwriting and use the O_TRUNC method if it has multiple links, but in practice almost no one does this,2 and even if you do, your write is no longer atomic, and there is still an unavoidable time-of-check to time-of-use race condition between checking the link count and overwriting the file.
Now suppose that your Obsidian vault is also a Git repository. As far as Git is concerned, the hard-linked files are two independent files, and Git operations will not respect the linkage:
git checkout,git restore, and other commands that overwrite files may break the links.- If the Git repository is cloned elsewhere, the two files in the clone will not be links at all, and their content can diverge. If you then pull back the divergent changes to the original repo, confusion will surely ensue.
Hard links seem appealing – symlinks that can never dangle! – but it is difficult for software to work with them reliably.
Further reading
- "Ghosts of Unix past, part 4: High-maintenance designs" by Neil Brown (LWN.net, 2010)
- "The trouble with symbolic links" by Chris Riddoch (LWN.net, 2022)
- "POSIX hardlink heartache" by Michael Orlitzky (2020) and "Dumb things you can sometimes do with hard links" (rachelbythebay, 2022) – Programs that run as root have to be very careful around hard links: imagine what happens if root does
chown -R alice /tmp/alice-stuffand/tmp/alice-stuff/innocuous.txtis a hard link to/etc/passwd.
-
https://forum.obsidian.md/t/request-allow-for-symlinks-within-vault-as-a-user-option/43962 ↩
-
I had Claude survey widely-used implementation of overwrite-by-
rename. Only some code in GLib checks the link count before overwriting. ↩