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
- Check whether the point is actually mounted before unmounting (unix.TestCheck or parse /proc/mounts).
- Identify processes holding the mount with lsof +f -- <mountPoint> or fuser -vm <mountPoint> and close them.
- If rclone lost track of the mount, unmount at the OS level: umount <mountPoint> (or fusermount -u / fusermount3 -uz for lazy), then retry.
- Normalize the mountPoint string (filepath.Clean, consistent absolute path) so it matches what was passed to Mount().
- 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
- Make unmount idempotent by checking /proc/mounts first.
- Close/cwd-out of processes under the mount before unmounting (lsof +f -- <point>).
- Never blind-retry unmount on EBUSY; clear holders first.
- Persist the exact mountPoint used at mount time and reuse it verbatim.
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.