Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chezmoi edit <target file> hardlink functionality breaks across btrfs subvolumes #3846

Closed
kjkent opened this issue Jul 1, 2024 · 1 comment · Fixed by #3847
Closed

chezmoi edit <target file> hardlink functionality breaks across btrfs subvolumes #3846

kjkent opened this issue Jul 1, 2024 · 1 comment · Fixed by #3847
Labels
enhancement New feature or request

Comments

@kjkent
Copy link

kjkent commented Jul 1, 2024

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

  1. 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'
  1. chezmoi edit ~/.config/zsh/.zshrc
  2. 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.

@twpayne twpayne added the enhancement New feature or request label Jul 1, 2024
@twpayne
Copy link
Owner

twpayne commented Jul 1, 2024

Thanks for the kind words!

What you identify is a known limitation of chezmoi edit --hardlink:

// Attempt to create the hard link. If this succeeds, continue to
// the next target. Hardlinking will fail if the temporary directory
// is on a different filesystem to the source directory, which is
// not the case for most users.
//
// FIXME create a temporary directory on the same filesystem as the
// source directory if needed.

Adding a tmpDir configuration variable makes sense. I'll add this.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 9, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants