k3s-io/k3s · error

failed %d link verifications

Error message

failed %d link verifications

What it means

VerifyLinks calls os.Readlink on each file listed in .links and compares the target against the expected value (mismatches logged as 'Link for file %s expected to be %s (fail)'). A non-zero mismatch count returns this error - required symlinks are missing (Readlink error counts as mismatch) or point at the wrong target.

Source

Thrown at pkg/dataverify/dataverify.go:80

	if err != nil {
		return err
	}
	if len(links) == 0 {
		return fmt.Errorf("no entries found in %s", linkListFile)
	}
	numFailed := 0
	for linkFile, linkExpected := range links {
		file := filepath.Join(root, linkFile)
		linkActual, _ := os.Readlink(file)
		if linkExpected != linkActual {
			logrus.Errorf("Link for file %s expected to be %s (fail)", linkFile, linkExpected)
			numFailed++
		} else {
			logrus.Debugf("Verified link %s is correct", linkFile)
		}
	}
	if numFailed != 0 {
		return fmt.Errorf("failed %d link verifications", numFailed)
	}
	return nil
}

func fileMapFields(fileName string, key, val int) (map[string]string, error) {
	file, err := os.Open(fileName)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	result := map[string]string{}
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		fields := strings.Fields(scanner.Text())
		if len(fields) == 0 {
			continue
		}
		if len(fields) <= key || len(fields) <= val {

View on GitHub (pinned to 6ba341e396)

Solutions

  1. Check the per-file 'Link for file ... (fail)' logs, then ls -l the named paths to see what they actually point to.
  2. Delete the temp data dir and let k3s re-extract natively on a POSIX filesystem (ext4/xfs) - do not pre-copy the data dir with tools that dereference symlinks.
  3. If a custom data-dir location is on a filesystem that cannot store symlinks, move it to one that can.

Example fix

# before (breaks symlinks)
scp -r /opt/k3s-data/ node:/var/lib/rancher/k3s/data/   # or tar with --dereference

# after (preserve symlinks)
tar -C /opt -cf - k3s-data | ssh node 'tar -C /var/lib/rancher/k3s/data/ -xf -'
Defensive patterns

Strategy: validation

Validate before calling

// Verify required symlinks survive staging:
links := parseLinks(filepath.Join(dir, ".links")) // map[link]target
for link, want := range links {
    got, err := os.Readlink(filepath.Join(dir, link))
    if err != nil || got != want {
        log.Fatalf("symlink %s: got %q err=%v want %q", link, got, err, want)
    }
}

Prevention

When it happens

Trigger: A file listed in .links is not a symlink, or its target differs from the recorded value, inside <data-dir>/data/<version>-tmp/bin (pkg/dataverify/dataverify.go:67-80).

Common situations: Extraction performed by a tool that flattened symlinks into copies (some Windows/SCP/NFS transfers); someone replaced a symlink with a real file; filesystem types (FAT) or containers not preserving symlinks; hand-restored data dir from tar without -h/--dereference awareness.

Related errors


AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15). Data as JSON: /api/errors/b16bea9516ddcb91. Report an issue: GitHub.