package api import ( "errors" "os" "strings" "git.misaka.ren/m1saka/dsh-web/internal/store" ) // Call only with the instance lock held, after Docker resources are removed. func (a *API) removeInstanceData(i store.Instance) error { for _, id := range []string{i.UID, i.ID} { if id == "" || id == "." || id == ".." || strings.ContainsAny(id, "/\\") { return errors.New("invalid instance storage path") } } for _, parts := range [][]string{{"workspaces", i.UID, "instances", i.ID}, {"backups", i.UID, i.ID}} { if err := a.removeDataDirectory(parts); err != nil { return err } } return nil } // Walk parents without following symlinks; RemoveAll unlinks child symlinks. func (a *API) removeDataDirectory(parts []string) error { root, err := os.OpenRoot(a.root) if err != nil { return err } defer func() { root.Close() }() for _, part := range parts[:len(parts)-1] { st, err := root.Lstat(part) if errors.Is(err, os.ErrNotExist) { return nil } if err != nil { return err } if !st.IsDir() { return errors.New("unsafe storage path") } next, err := root.OpenRoot(part) if err != nil { return err } actual, err := next.Stat(".") if err != nil || !os.SameFile(st, actual) { next.Close() return errors.New("storage path changed") } root.Close() root = next } return root.RemoveAll(parts[len(parts)-1]) }