IceWhaleTech/CasaOS · error

unmount failed

Error message

unmount failed

What it means

Returned by Unmount() in pkg/utils/httper/drive.go when POST /mount/unmount responds non-200. rclone refuses unmount when the given mountPoint is not currently mounted, or when files under it are held open by processes (EBUSY). The response body containing rclone's reason is logged but not included in the returned error.

Source

Thrown at pkg/utils/httper/drive.go:106

		return err
	}
	if res.StatusCode() != 200 {
		return fmt.Errorf("mount failed")
	}
	logger.Info("mount then", zap.Any("res", res.Body()))
	return nil
}
func Unmount(mountPoint string) error {
	res, err := NewRestyClient().R().SetFormData(map[string]string{
		"mountPoint": mountPoint,
	}).Post("/mount/unmount")
	if err != nil {
		logger.Error("when unmount", zap.Error(err))
		return err
	}
	if res.StatusCode() != 200 {
		logger.Error("then unmount failed", zap.Any("res", res.Body()))
		return fmt.Errorf("unmount failed")
	}

	logger.Info("unmount then", zap.Any("res", res.Body()))
	return nil
}

func CreateConfig(data map[string]string, name, t string) error {
	data["config_is_local"] = "false"
	dataStr, _ := json.Marshal(data)
	res, err := NewRestyClient().R().SetFormData(map[string]string{
		"name":       name,
		"parameters": string(dataStr),
		"type":       t,
	}).Post("/config/create")
	logger.Info("when create config then", zap.Any("res", res.Body()))
	if err != nil {
		return err
	}

View on GitHub (pinned to 0d3b2f444e)

Solutions

  1. Check whether the point is actually mounted before unmounting (unix.TestCheck or parse /proc/mounts).
  2. Identify processes holding the mount with lsof +f -- <mountPoint> or fuser -vm <mountPoint> and close them.
  3. If rclone lost track of the mount, unmount at the OS level: umount <mountPoint> (or fusermount -u / fusermount3 -uz for lazy), then retry.
  4. Normalize the mountPoint string (filepath.Clean, consistent absolute path) so it matches what was passed to Mount().
  5. Include status and body in the error for diagnosability.

Example fix

// before
if res.StatusCode() != 200 {
    logger.Error("then unmount failed", zap.Any("res", res.Body()))
    return fmt.Errorf("unmount failed")
}

// after
if res.StatusCode() != 200 {
    logger.Error("then unmount failed", zap.Any("res", res.Body()))
    return fmt.Errorf("unmount failed: status=%d body=%s", res.StatusCode(), res.Body())
}
Defensive patterns

Strategy: validation

Validate before calling

// before calling Unmount()
data, err := os.ReadFile("/proc/mounts")
if err == nil && !strings.Contains(string(data), " "+mountPoint+" ") {
    return nil // idempotent: nothing to unmount
}

Try / catch

if err := httper.Unmount(mountPoint); err != nil {
    if isMounted(mountPoint) {
        // rclone failed but kernel still has it: force OS-level unmount
        if uerr := exec.Command("fusermount", "-uz", mountPoint).Run(); uerr != nil {
            return fmt.Errorf("unmount failed (%v) and lazy unmount failed (%v)", err, uerr)
        }
    }
    return err
}

Prevention

When it happens

Trigger: POST /mount/unmount with mountPoint that (1) was never mounted or already unmounted, (2) has open file handles (shell cwd inside the mount, running process reading files) so the kernel reports device busy, (3) rclone daemon restarted so its internal mount registry no longer knows the mount, (4) mountPoint string differs from the one used at mount time (trailing slash, symlinked path).

Common situations: CasaOS UI 'remove drive' clicked twice or after drive service restart; user has a terminal or file browser sitting inside the mount; stale entry in /proc/mounts after a hard reboot; path canonicalization mismatch between mount and unmount calls.

Related errors


AI-assisted analysis of IceWhaleTech/CasaOS@0d3b2f444e (2026-08-15). Data as JSON: /api/errors/b7ad22709232d938. Report an issue: GitHub.