{"record":{"id":"e6e180144c3e8332","repo":"ppy/osu","slug":"destination-provided-is-inside-the-source","errorCode":null,"errorMessage":"Destination provided is inside the source","messagePattern":"Destination provided is inside the source","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"osu.Game/IO/MigratableStorage.cs","lineNumber":57,"sourceCode":"        /// <summary>\r\n        /// A general purpose migration method to move the storage to a different location.\r\n        /// <param name=\"newStorage\">The target storage of the migration.</param>\r\n        /// </summary>\r\n        /// <returns>Whether cleanup could complete.</returns>\r\n        public virtual bool Migrate(Storage newStorage)\r\n        {\r\n            var source = new DirectoryInfo(GetFullPath(\".\"));\r\n            var destination = new DirectoryInfo(newStorage.GetFullPath(\".\"));\r\n\r\n            // using Uri is the easiest way to check equality and contains (https://stackoverflow.com/a/7710620)\r\n            var sourceUri = new Uri(source.FullName + Path.DirectorySeparatorChar);\r\n            var destinationUri = new Uri(destination.FullName + Path.DirectorySeparatorChar);\r\n\r\n            if (sourceUri == destinationUri)\r\n                throw new ArgumentException(\"Destination provided is already the current location\", destination.FullName);\r\n\r\n            if (sourceUri.IsBaseOf(destinationUri))\r\n                throw new ArgumentException(\"Destination provided is inside the source\", destination.FullName);\r\n\r\n            // ensure the new location has no files present, else hard abort\r\n            if (destination.Exists)\r\n            {\r\n                if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0)\r\n                    throw new ArgumentException(\"Destination provided already has files or directories present\", destination.FullName);\r\n            }\r\n\r\n            CopyRecursive(source, destination);\r\n            ChangeTargetStorage(newStorage);\r\n\r\n            return DeleteRecursive(source);\r\n        }\r\n\r\n        protected bool DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true)\r\n        {\r\n            bool allFilesDeleted = true;\r\n\r","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/IO/MigratableStorage.cs#L39-L75","documentation":"Thrown by MigratableStorage.Migrate when destinationUri is inside sourceUri (Uri.IsBaseOf). Moving the data store into one of its own subdirectories would create recursion and data corruption, so it is hard-rejected before CopyRecursive runs.","triggerScenarios":"Calling Migrate(newStorage) where the target path is a subdirectory of the current storage root — e.g. migrating osu!/ into osu!/backup/ . The IsBaseOf check catches nested locations.","commonSituations":"User selects a folder inside the current data directory as the new location; default migration path derived from the current path by appending a subfolder; nested install layouts.","solutions":["Reject destinations that are base-relative to the source in the picker UI before calling Migrate.","Choose a destination on a different volume or a sibling directory that is provably not under the source.","If a nested layout is genuinely desired, copy out manually first then re-point storage, avoiding Migrate's guard."],"exampleFix":"// before\nstorage.Migrate(new DesktopStorage(Path.Combine(currentRoot, \"backup\"), host)); // throws\n\n// after\nstring dst = Path.GetFullPath(userChosenPath + Path.DirectorySeparatorChar);\nstring src = Path.GetFullPath(storage.GetFullPath(\".\") + Path.DirectorySeparatorChar);\nif (new Uri(src).IsBaseOf(new Uri(dst)))\n    throw new InvalidOperationException(\"Cannot migrate into a subdirectory of the current storage.\");\nstorage.Migrate(new DesktopStorage(userChosenPath, host));","handlingStrategy":"validation","validationCode":"var srcUri = new Uri(Path.GetFullPath(storage.GetFullPath(\".\") + Path.DirectorySeparatorChar));\nvar dstUri = new Uri(Path.GetFullPath(newPath + Path.DirectorySeparatorChar));\nif (srcUri.IsBaseOf(dstUri)) throw new InvalidOperationException(\"Destination is inside source.\");","typeGuard":"static bool IsDestinationInsideSource(string source, string dest)\n    => new Uri(Path.GetFullPath(source) + Path.DirectorySeparatorChar)\n       .IsBaseOf(new Uri(Path.GetFullPath(dest) + Path.DirectorySeparatorChar));","tryCatchPattern":"try { storage.Migrate(target); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"inside the source\"))\n{ /* prompt user to choose a folder outside the current data dir */ }","preventionTips":["Filter the folder picker to exclude the current data root and its descendants.","Prefer destinations on a different volume to avoid nesting entirely.","Document that the data store cannot be moved into its own subdirectory."],"tags":["filesystem","migration","storage","validation","safety"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}