`chezmoi edit <target file>` hardlink functionality breaks across btrfs subvolumes
kjkent opened this issue · comments
Describe the bug
Edit: I realise this is probably moreso a feature request rather than a bug.
When used on a btrfs filesystem, chezmoi does not attempt to detect or workaround btrfs subvolumes. Using chezmoi edit <target file>
on a target dotfile (e.g. ${XDG_CONFIG_HOME}/zsh/.zshrc
) opens the linked, renamed source file (e.g. ${XDG_DATA_HOME}/chezmoi/dot_config/zsh/dot_zshrc
), rather than a hardlinked file with a matching extension to the target. This prevents syntax highlighting in this use case.
The cause of this appears to be that chezmoi creates these hardlinks in /tmp
, seemingly without a way to override this. If unaware, btrfs subvolumes are essentially folders with a few special properties. Without getting too into the deets here, they're often used to demarcate backup boundaries (btrfs folk might get annoyed at me saying that as they're technically snapshots) and btrfs does not permit cross-subvolume hardlinks.
/home/<user>
and /
are frequently on different subvolumes due to different backup snapshot needs. Cache dirs are often on different subvolumes also to block them from other snapshots and, /tmp is...
😆 Hang on a minute, isn't /tmp
often in tmpfs, which would block hardlinks? I'm unsure of this.
Would it be feasible to allow tmpDir
to be set as a config variable in the same way as scriptTmpDir
, falling back to what's provided by TmpDir()
if unset/invalid? I've had a look through the source but much of it is over my head.
To reproduce
- Install chezmoi, add .config/zsh/.zshrc, create the following config:
[diff]
pager = 'diff-so-fancy | less --tabs=4 -RFX'
reverse = true
[edit]
watch = true
hardlink = true
[git]
autoAdd = true
[gpg]
recipients = ['kris@kjkent.dev']
suffix = '.asc.gpg'
chezmoi edit ~/.config/zsh/.zshrc
- Observe path of opened file in editor (neovim in my case) to be
.local/share/chezmoi/dot_config/zsh/dot_zshrc
Expected behavior
As per the docs, chezmoi opens a hardlinked .zshrc
file in a temp dir within the source directory.
Output of command with the --verbose
flag
--verbose
returned no extra output alongside opening nvim
, so ran with --debug
instead, which produced the below on editor exit:
λ chezmoi --debug edit ~/.config/zsh/.zshrc
# editor opens, and is closed
time=2024-07-01T16:51:04.885+01:00 level=INFO msg=persistentPreRunRootE version.version=2.49.1 version.commit="" version.date=2024-06-24T08:14:39Z version.builtBy="" args="[chezmoi edit --debug .config/zsh/.zshrc]" goVersion=go1.22.4
time=2024-07-01T16:51:04.885+01:00 level=INFO msg=Stat component=system name=/home/kjkent/.config/chezmoi/chezmoistate.boltdb
time=2024-07-01T16:51:04.885+01:00 level=INFO msg=Stat component=system name=/home/kjkent/.local/share/chezmoi
time=2024-07-01T16:51:04.885+01:00 level=ERROR msg=Mkdir component=system err="mkdir /run/user/1000: file exists" name=/run/user/1000 perm=448
time=2024-07-01T16:51:04.885+01:00 level=INFO msg=Stat component=system name=/run/user/1000
time=2024-07-01T16:51:04.885+01:00 level=INFO msg=Stat component=system name=/home/kjkent/.local/share/chezmoi/.git
time=2024-07-01T16:51:04.886+01:00 level=ERROR msg=ReadFile component=system err="open /home/kjkent/.local/share/chezmoi/.chezmoiroot: no such file or directory" name=/home/kjkent/.local/share/chezmoi/.chezmoiroot size=0 data=""
time=2024-07-01T16:51:04.887+01:00 level=ERROR msg=ReadFile component=system err="open /home/kjkent/.local/share/chezmoi/.chezmoiversion: no such file or directory" name=/home/kjkent/.local/share/chezmoi/.chezmoiversion size=0 data=""
time=2024-07-01T16:51:04.887+01:00 level=INFO msg=Stat component=system name=/home/kjkent/.local/share/chezmoi
time=2024-07-01T16:51:04.887+01:00 level=INFO msg=Stat component=system name=/home/kjkent/.local/share/chezmoi
time=2024-07-01T16:51:04.887+01:00 level=INFO msg=ReadDir component=system name=/home/kjkent/.local/share/chezmoi
time=2024-07-01T16:51:04.887+01:00 level=INFO msg=ReadFile component=system name=/home/kjkent/.local/share/chezmoi/.chezmoiignore size=15 data="**/*-lock.json\n"
##### snipped: chezmoi listing all managed paths in the format:
time=2024-07-01T16:51:07.059+01:00 level=INFO msg=ReadDir component=system
name=<managed path>
##### chezmoi attempts to create tmp file before directly opening source file:
time=2024-07-01T16:51:04.890+01:00 level=INFO msg=MkdirTemp tempDir=/tmp/chezmoi-edit2036013460
time=2024-07-01T16:51:04.890+01:00 level=ERROR msg=Link component=system err="link /home/kjkent/.local/share/chezmoi/dot_config/zsh/dot_zshrc /tmp/chezmoi-edit2036013460/.config/zsh/.zshrc: invalid cross-device link" oldpath=/home/kjkent/.local/share/chezmoi/dot_config/zsh/dot_zshrc newpath=/tmp/chezmoi-edit2036013460/.config/zsh/.zshrc
time=2024-07-01T16:51:04.890+01:00 level=INFO msg=Close component=persistentState
time=2024-07-01T16:51:07.056+01:00 level=INFO msg=Run cmd="/usr/bin/nvim /home/kjkent/.local/share/chezmoi/dot_config/zsh/dot_zshrc" duration=2.16661488s err=<nil>
time=2024-07-01T16:51:07.057+01:00 level=INFO msg=ReadDir component=system name=/home/kjkent/.local/share/chezmoi
time=2024-07-01T16:51:07.057+01:00 level=ERROR msg=Mkdir component=system err="mkdir /home/kjkent/.config/chezmoi: file exists" name=/home/kjkent/.config/chezmoi perm=511
time=2024-07-01T16:51:07.057+01:00 level=INFO msg=Stat component=system name=/home/kjkent/.config/chezmoi
time=2024-07-01T16:51:07.057+01:00 level=INFO msg=Get component=persistentState bucket=configState key=configState value=""
time=2024-07-01T16:51:07.057+01:00 level=ERROR msg=ReadFile component=system err="open /home/kjkent/.local/share/chezmoi/.chezmoiversion: no such file or directory" name=/home/kjkent/.local/share/chezmoi/.chezmoiversion size=0 data=""
time=2024-07-01T16:51:07.057+01:00 level=INFO msg=Stat component=system name=/home/kjkent/.local/share/chezmoi
time=2024-07-01T16:51:07.057+01:00 level=INFO msg=Stat component=system name=/home/kjkent/.local/share/chezmoi
time=2024-07-01T16:51:07.057+01:00 level=INFO msg=ReadDir component=system name=/home/kjkent/.local/share/chezmoi
time=2024-07-01T16:51:07.057+01:00 level=INFO msg=ReadFile component=system name=/home/kjkent/.local/share/chezmoi/.chezmoiignore size=15 data="**/*-lock.json\n"
time=2024-07-01T16:51:07.059+01:00 level=INFO msg=ReadDir component=system name=/home/kjkent/.local/share/chezmoi/.chezmoiscripts
##### snipped: chezmoi re-lists all managed paths
time=2024-07-01T16:51:07.063+01:00 level=INFO msg=ReadFile component=system name=/home/kjkent/.local/share/chezmoi/dot_config/zsh/dot_zshrc size=9653 data="# Enable Powerlevel10k instant prompt. Should stay close to the ..."
time=2024-07-01T16:51:07.063+01:00 level=INFO msg=Lstat component=system name=/home/kjkent/.config/zsh/.zshrc
time=2024-07-01T16:51:07.063+01:00 level=INFO msg=Get component=persistentState bucket=entryState key=/home/kjkent/.config/zsh/.zshrc value="{\n \"type\": \"file\",\n \"mode\": 416,\n \"contentsSHA256\": \"baa764641d234ba8c1e4b6cbf1d4ca155a2a13713f3fe44b3cba80cd086c40cc\"\n}\n"
time=2024-07-01T16:51:07.063+01:00 level=INFO msg=ReadFile component=system name=/home/kjkent/.config/zsh/.zshrc size=9653 data="# Enable Powerlevel10k instant prompt. Should stay close to the ..."
time=2024-07-01T16:51:07.063+01:00 level=INFO msg=defaultPreApplyFunc targetRelPath=.config/zsh/.zshrc targetEntryState.Type=file targetEntryState.Mode=416 targetEntryState.ContentsSHA256=baa764641d234ba8c1e4b6cbf1d4ca155a2a13713f3fe44b3cba80cd086c40cc targetEntryState.contents="# Enable Powerlevel10k instant prompt. Should stay close to the ..." lastWrittenEntryState.Type=file lastWrittenEntryState.Mode=416 lastWrittenEntryState.ContentsSHA256=baa764641d234ba8c1e4b6cbf1d4ca155a2a13713f3fe44b3cba80cd086c40cc actualEntryState.Type=file actualEntryState.Mode=416 actualEntryState.ContentsSHA256=baa764641d234ba8c1e4b6cbf1d4ca155a2a13713f3fe44b3cba80cd086c40cc actualEntryState.contents="# Enable Powerlevel10k instant prompt. Should stay close to the ..."
time=2024-07-01T16:51:07.075+01:00 level=INFO msg=Close component=persistentState
time=2024-07-01T16:51:07.077+01:00 level=INFO msg=Run cmd="/usr/bin/git add ." duration=2.374541ms err=<nil>
time=2024-07-01T16:51:07.080+01:00 level=INFO msg=Output cmd="/usr/bin/git status --porcelain=v2" duration=2.977361ms size=1130 output="1 M. N... 100644 100644 100644 223fd130499819cebf8a4cc0a2b85d71e..." err=<nil>
time=2024-07-01T16:51:07.081+01:00 level=INFO msg=RemoveAll tempDir=/tmp/chezmoi-edit2036013460
Output of chezmoi doctor
λ chezmoi doctor
RESULT CHECK MESSAGE
warning version v2.49.1, built at 2024-06-24T08:14:39Z
ok latest-version v2.49.1
ok os-arch linux/amd64 (Arch Linux)
ok uname Linux kdes 6.9.7-arch1-1 #1 SMP PREEMPT_DYNAMIC Fri, 28 Jun 2024 04:32:50 +0000 x86_64 GNU/Linux
ok go-version go1.22.4 (gc)
ok executable /usr/bin/chezmoi
ok config-file ~/.config/chezmoi/chezmoi.toml, last modified 2024-07-01T17:00:22+01:00
warning source-dir ~/.local/share/chezmoi is a git working tree (dirty)
ok suspicious-entries no suspicious entries
warning working-tree ~/.local/share/chezmoi is a git working tree (dirty)
ok dest-dir ~ is a directory
warning umask 027
ok cd-command found /usr/bin/zsh
ok cd-args /usr/bin/zsh
info diff-command not set
ok edit-command found /usr/bin/nvim
ok edit-args /usr/bin/nvim
ok git-command found /usr/bin/git, version 2.45.2
ok merge-command found /usr/bin/vimdiff
ok shell-command found /usr/bin/zsh
ok shell-args /usr/bin/zsh
info age-command age not found in $PATH
ok gpg-command found /usr/bin/gpg, version 2.4.5
info pinentry-command not set
info 1password-command op not found in $PATH
info bitwarden-command bw not found in $PATH
info bitwarden-secrets-command bws not found in $PATH
info dashlane-command dcli not found in $PATH
info doppler-command doppler not found in $PATH
info gopass-command gopass not found in $PATH
info keepassxc-command keepassxc-cli not found in $PATH
info keepassxc-db not set
info keeper-command keeper not found in $PATH
info lastpass-command lpass not found in $PATH
info pass-command pass not found in $PATH
info passhole-command ph not found in $PATH
info rbw-command rbw not found in $PATH
info vault-command vault not found in $PATH
info vlt-command vlt not found in $PATH
info secret-command not set
Additional context
Chezmoi is awesome, and I appreciate all the devs who've worked on it.
Thanks for the kind words!
What you identify is a known limitation of chezmoi edit --hardlink
:
chezmoi/internal/cmd/editcmd.go
Lines 144 to 150 in 4241393
Adding a tmpDir
configuration variable makes sense. I'll add this.