Skip to content

[BUG] ZVM on windows fails to make a symlink on windows for non-admin users #79

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

Open
timsavage opened this issue Apr 22, 2024 · 6 comments
Assignees
Labels
bug Something isn't working help wanted Extra attention is needed

Comments

@timsavage
Copy link

Describe the bug
ZVM is asking for administrative privileges to create a link on windows between an installed version and the common ~/.zvm/bin folder.

As both of these locations are within the users home folder (C:\users\USERNAME) these should not be required.

While Administrative privileges are available for personal computers, this is not always an option if your computer is managed by a company.

To Reproduce
Steps to reproduce the behavior:

  1. When running as a user that does not have Administrator priviledges
  2. Run zvm use ...
  3. See error

2024/02/22 10:34:50 ERRO unable to symlink as Administrator
symlink C:\Users\USERNAME.zvm\0.12.0 C:\Users\USERNAME.zvm\bin: A required privilege is not held by the client.

Expected behavior
Link from ~/.zvm/VERSION is created to ~/.zvm/bin

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: Windows 11
  • Architecture: amd64
  • Version: 0.6.7

Configuration:

  • VMU (Version Map URL) or alias: default

Additional context
Add any other context about the problem here.

@tristanisham
Copy link
Owner

Okay, thanks Tim. Could you re-run the commands you've been trying with ZVM_DEBUG=1 as an environment variable? It should give us more info.

@tristanisham tristanisham self-assigned this May 7, 2024
@tristanisham tristanisham added bug Something isn't working help wanted Extra attention is needed labels May 7, 2024
@elerch
Copy link

elerch commented Feb 7, 2025

I noticed this in go's stdlib for creation of symbolic links: https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/os/file_windows.go;l=339

It appears you need Windows 10 1703 or higher in developer mode to create symlinks without administrator: https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/

zvm could detect the situation, but then it isn't obvious what to do...Administrator escalation might be ok. Perhaps zvm needs an option to copy rather than symlink?

@AlexBlandin
Copy link

In general, the symlink situation on Windows is often not worth it for most devs, as most users will now need Developer Mode and UAC elevation to make a symlink. Other tool-chains that use linking to avoid copies, such as uv, tend to hard link files on Windows, avoiding UAC elevation for most.

Since zvm only needs to manage zig and zls within its own directories, this is a smaller footprint than package managers deal with, so will be easier to manage. It's not what we want, but for Windows, it avoids the hassle with negligible file-system overhead — since this often eschews junctions for only hard links, and instead replicates the directory structure and populates it with hard links. There's plenty of prior art here, which should help with making a balanced decision.

There's a brief table of Windows' link types at https://ss64.com/nt/mklink.html

@tristanisham
Copy link
Owner

I believe ZVM already escalates itself to administrator to create the necessary links. I'm just leaving for a week long trip today so I won't be able to engage much with this issue until next week. I'm interested any solution that is more reliable.

@AlexBlandin
Copy link

AlexBlandin commented Mar 2, 2025

On Windows, escalating to administrator requires a UAC prompt even for administrator users (since you should not turn off the prompt), and will not work for any users who do not have permissions or are unable to access an administrator in a timely fashion (e.g. for a large corporate or university network). This means that symlinks, while technically a cleaner and nicer option even on Windows, is not robust and cannot install while unattended.

Switching to hard links as the default on Windows would mean no escalation is required, while the current behaviour could be preserved behind a switch. The established default behaviour is typically hard link on Windows/Linux and clone on macOS, as both bun and uv do; see Bun's "Installation Strategies" (collapsed by default) and uv's "link-mode" config for more.

@tristanisham
Copy link
Owner

I finally had some bandwidth to take a look at this, and I think going with hardlinks is probably the best step going forward. The only issue I foresee is that if you remove a version of Zig you have installed that's set as your default, it will hang around until you switch to a new version.

This is the working updated Link code for Windows

// Link is a wrapper around Go's os.Symlink and os.Link functions,
// On Windows, if Link is unable to create a symlink it will attempt to create a
// hardlink before trying its automatic privilege escalation.
func Link(oldname, newname string) error {
	// Attempt to do a regular symlink if allowed by user's permissions
	if err := os.Symlink(oldname, newname); err != nil {
		// If that fails, try to create an old hardlink.
		if err := os.Link(oldname, newname); err == nil {
			return nil
		}
		// If creating a hardlink fails, check to see if the user is an admin.
		// If they're not an admin, try to become an admin and retry making a symlink.
		if !isAdmin() {
			log.Error("Symlink & Hardlink failed", "admin", false)

			// If not already admin, try to become admin
			if adminErr := becomeAdmin(); adminErr != nil {
				return errors.Join(ErrWinEscToAdmin, adminErr, err)
			}

			if err := os.Symlink(oldname, newname); err != nil {
				if err := os.Link(oldname, newname); err == nil {
					return nil
				}

				return errors.Join(ErrEscalatedSymlink, ErrEscalatedHardlink, err)
			}

			return nil
		}

		return errors.Join(ErrEscalatedSymlink, ErrEscalatedHardlink, err)

	}

	return nil
}

You can find it in cli/meta/link_windows.go. The Unix version is unchanged.

I'd appreciate anyone having issues with Symlinks on Windows download the latest Master and let me know if this works for them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants