{"record":{"id":"7176f5668fbc53b2","repo":"AlistGo/alist","slug":"cannot-copy-parent-dir-to-child-7176f5","errorCode":null,"errorMessage":"cannot copy parent dir to child","messagePattern":"cannot copy parent dir to child","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/url_tree/driver.go","lineNumber":214,"sourceCode":"\t\treturn nil, errs.PermissionDenied\n\t}\n\td.mutex.Lock()\n\tdefer d.mutex.Unlock()\n\tsrcNode := GetNodeFromRootByPath(d.root, srcObj.GetPath())\n\tif srcNode == nil {\n\t\treturn nil, errs.ObjectNotFound\n\t}\n\tsrcNode.Name = newName\n\td.updateStorage()\n\treturn nodeToObj(srcNode, stdpath.Join(stdpath.Dir(srcObj.GetPath()), newName))\n}\n\nfunc (d *Urls) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {\n\tif !d.Writable {\n\t\treturn nil, errs.PermissionDenied\n\t}\n\tif strings.HasPrefix(dstDir.GetPath(), srcObj.GetPath()) {\n\t\treturn nil, errors.New(\"cannot copy parent dir to child\")\n\t}\n\td.mutex.Lock()\n\tdefer d.mutex.Unlock()\n\tdstNode := GetNodeFromRootByPath(d.root, dstDir.GetPath())\n\tif dstNode == nil || dstNode.isFile() {\n\t\treturn nil, errs.NotFolder\n\t}\n\tsrcNode := GetNodeFromRootByPath(d.root, srcObj.GetPath())\n\tif srcNode == nil {\n\t\treturn nil, errs.ObjectNotFound\n\t}\n\tnewNode := srcNode.deepCopy(dstNode.Level + 1)\n\tdstNode.Children = append(dstNode.Children, newNode)\n\td.root.calSize()\n\td.updateStorage()\n\treturn nodeToObj(newNode, stdpath.Join(dstDir.GetPath(), stdpath.Base(srcObj.GetPath())))\n}\n","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/url_tree/driver.go#L196-L232","documentation":"The url_tree driver's Copy() applies the same cycle guard as Move: copying a directory into its own subtree would recurse forever when deepCopy walks the source while it is being attached under it, so the operation is refused up front. Identical HasPrefix-based check as the move guard, including its segment-boundary blind spot.","triggerScenarios":"Copy(src=/music, dst=/music/backup): destination path starts with source path, rejected. Also (false positive) Copy(src=/music, dst=/music-old) because HasPrefix matches on raw string prefix without requiring a '/' boundary.","commonSituations":"Attempting to back a folder up into itself via copy; Recursive copy features that keep the destination inside the source tree; Sibling-folder name-prefix collisions triggering the guard unexpectedly","solutions":["Copy to a destination outside the source subtree","Rename the destination first if it merely shares a name prefix with the source","Apply the same segment-aware prefix fix as for Move (dst == src || HasPrefix(dst, src+\"/\")) in a local fork or upstream patch"],"exampleFix":"// before\nif strings.HasPrefix(dstDir.GetPath(), srcObj.GetPath()) {\n\treturn nil, errors.New(\"cannot copy parent dir to child\")\n}\n\n// after\nsrc := strings.TrimSuffix(srcObj.GetPath(), \"/\")\nif dstDir.GetPath() == src || strings.HasPrefix(dstDir.GetPath(), src+\"/\") {\n\treturn nil, errors.New(\"cannot copy parent dir to child\")\n}","handlingStrategy":"validation","validationCode":"src := strings.TrimSuffix(srcObj.GetPath(), \"/\")\nif dstDir.GetPath() == src || strings.HasPrefix(dstDir.GetPath(), src+\"/\") {\n    return fmt.Errorf(\"cannot copy %s into its own subtree %s\", src, dstDir.GetPath())\n}","typeGuard":"func isCopyIntoSelf(src, dst string) bool {\n    src = strings.TrimSuffix(src, \"/\")\n    return dst == src || strings.HasPrefix(dst, src+\"/\")\n}","tryCatchPattern":"if _, err := d.Copy(ctx, srcObj, dstDir); err != nil {\n    if strings.Contains(err.Error(), \"cannot copy parent dir to child\") {\n        // copy to a sibling outside the subtree, e.g. /backup instead of /music/backup\n    }\n}","preventionTips":["Pre-validate copy destinations for subtree containment","Avoid destination names that merely extend the source name ('/data' -> '/data2') given the raw-prefix check","Guard recursive copy helpers with a depth/visited set as a second line of defense"],"tags":["url-tree","copy","filesystem","cycle-prevention"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}