{"record":{"id":"81e96e56a391497d","repo":"MuntashirAkon/AppManager","slug":"couldn-t-create-symbolic-link-file-pointing-to-linkname","errorCode":null,"errorMessage":"Couldn't create symbolic link ${file} pointing to ${linkName}","messagePattern":"Couldn't create symbolic link (.+?) pointing to (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"app/src/main/java/io/github/muntashirakon/AppManager/utils/TarUtils.java","lineNumber":188,"sourceCode":"                        file.delete();\n                        continue;\n                    }\n                    // Check if the given entry is a link.\n                    if (entry.isSymbolicLink() && file.getFilePath() != null) {\n                        if ((!Paths.isUnderFilter(file, dest, filterPatterns) || Paths.willExclude(file, dest, exclusionPatterns))) {\n                            // Do not create this link even if it is a directory\n                            continue;\n                        }\n                        String linkName = entry.getLinkName();\n                        // There's no need to check if the linkName exists as it may be extracted\n                        // after the link has been created\n                        // Special check for /data/app\n                        if (linkName.startsWith(\"/data/app/\")) {\n                            linkName = getAbsolutePathToDataApp(linkName, realDataAppPath);\n                        }\n                        file.delete();\n                        if (!file.createNewSymbolicLink(linkName)) {\n                            throw new IOException(\"Couldn't create symbolic link \" + file + \" pointing to \" + linkName);\n                        }\n                        continue;  // links do not need permission fixes\n                    } else {\n                        // Zip slip vulnerability might still be present\n                        String realFilePath = file.getRealFilePath();\n                        if (realDestPath != null && realFilePath != null && !realFilePath.startsWith(realDestPath)) {\n                            throw new IOException(\"Zip slip vulnerability detected!\" +\n                                    \"\\nExpected dest: \" + new File(realDestPath, entry.getName()) +\n                                    \"\\nActual path: \" + realFilePath);\n                        }\n                        if (!entry.isDirectory()) {\n                            try (OutputStream os = file.openOutputStream()) {\n                                IoUtils.copy(tis, os);\n                            }\n                        }\n                    }\n                    // Fix permissions\n                    TarArchiveEntry finalEntry = entry;","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/app/src/main/java/io/github/muntashirakon/AppManager/utils/TarUtils.java#L170-L206","documentation":"While extracting a tar entry that is a symbolic link, TarUtils.extract deletes any existing file at the target location and calls ExtendedFile.createNewSymbolicLink(linkName). If the OS-level symlink creation fails (returns false), it throws IOException(\"Couldn't create symbolic link <file> pointing to <linkName>\"). Common underlying causes are filesystems that do not support symlinks (FAT/exFAT, some external storage) or lack of permission.","triggerScenarios":"Extracting an archive containing symlink entries onto a destination filesystem without symlink support (vFAT SD card, /sdcard FUSE restrictions), or in a location the app lacks write/create permission for; Android may also block symlink creation depending on the mount and target path.","commonSituations":"Extracting app backups or APK splits with relative symlinks to external storage; restoring archives to shared storage that silently converts or rejects symlinks; SELinux policies denying symlink creation in the destination.","solutions":["Extract to internal app storage (context.getFilesDir()) which supports symlinks, instead of external/shared storage","Check/create a test symlink in the destination beforehand and warn the user if unsupported","Handle the failed createNewSymbolicLink by copying the link target's content as a regular file fallback","Catch IOException around extract and report which file/link failed"],"exampleFix":"// before\nTarUtils.extract(tarIn, destDir, ...);\n// after\ntry {\n    TarUtils.extract(tarIn, destDir, ...);\n} catch (IOException e) {\n    if (e.getMessage().startsWith(\"Couldn't create symbolic link\")) {\n        Log.w(TAG, \"Symlinks unsupported on \" + destDir + \"; extracting without links\");\n        TarUtils.extract(tarIn, destDir, ... /* symlinkFallback=true or filter links */);\n    } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"File probe = new File(destDir, \".symlink_probe\");\nboolean symlinksSupported = probe.createNewSymbolicLink(\"probe_target\");\nprobe.delete();","typeGuard":"boolean supportsSymlinks(File dir) {\n    File p = new File(dir, \".probe\" + System.nanoTime());\n    try { return p.createNewSymbolicLink(\"x\") | p.delete(); } catch (IOException e) { return false; }\n}","tryCatchPattern":"try {\n    TarUtils.extract(in, dest, filters);\n} catch (IOException e) {\n    if (e.getMessage().startsWith(\"Couldn't create symbolic link\")) {\n        // fallback: re-extract with links skipped or copy link targets\n    } else throw e;\n}","preventionTips":["Extract symlink-bearing archives only to internal storage that supports symlinks","Probe symlink support in the destination before extraction","Avoid vFAT/exFAT and FUSE-mounted shared storage for archive restore"],"tags":["android","symlink","archive","filesystem"],"backgroundTag":"file-write-failed","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}