{"record":{"id":"d88d4f8524e147f8","repo":"apache/hadoop","slug":"possible-cyclic-loop-while-following-symbolic-link-d88d4f","errorCode":null,"errorMessage":"Possible cyclic loop while following symbolic link \" + original","messagePattern":"Possible cyclic loop while following symbolic link \" \\+ original","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/Stat.java","lineNumber":124,"sourceCode":"          \"stat is not supported on this platform\");\n    }\n  }\n\n  @Override\n  protected void parseExecResult(BufferedReader lines) throws IOException {\n    // Reset stat\n    stat = null;\n\n    String line = lines.readLine();\n    if (line == null) {\n      throw new IOException(\"Unable to stat path: \" + original);\n    }\n    if (line.endsWith(\"No such file or directory\") ||\n        line.endsWith(\"Not a directory\")) {\n      throw new FileNotFoundException(\"File \" + original + \" does not exist\");\n    }\n    if (line.endsWith(\"Too many levels of symbolic links\")) {\n      throw new IOException(\"Possible cyclic loop while following symbolic\" +\n          \" link \" + original);\n    }\n    // 6,symbolic link,6,1373584236,1373584236,lrwxrwxrwx,andrew,andrew,`link' -> `target'\n    // OR\n    // 6,symbolic link,6,1373584236,1373584236,lrwxrwxrwx,andrew,andrew,'link' -> 'target'\n    StringTokenizer tokens = new StringTokenizer(line, \",\");\n    try {\n      long length = Long.parseLong(tokens.nextToken());\n      boolean isDir = tokens.nextToken().equalsIgnoreCase(\"directory\") ? true\n          : false;\n      // Convert from seconds to milliseconds\n      long modTime = Long.parseLong(tokens.nextToken())*1000;\n      long accessTime = Long.parseLong(tokens.nextToken())*1000;\n      String octalPerms = tokens.nextToken();\n      // FreeBSD has extra digits beyond 4, truncate them\n      if (octalPerms.length() > 4) {\n        int len = octalPerms.length();\n        octalPerms = octalPerms.substring(len-4, len);","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/Stat.java#L106-L142","documentation":"Thrown in Stat.parseExecResult when stat output ends with 'Too many levels of symbolic links' (ELOOP). Stat catches this specific errno text and rethrows as IOException flagging a probable cycle, because following the symlink chain (dereference mode, stat -L) hit the kernel's symlink-depth limit.","triggerScenarios":"new Stat(path, true, shell) (dereference=true) where path is a symlink that points back into its own chain: a -> b, b -> a, or link pointing to an ancestor directory containing itself.","commonSituations":"Build/cleanup scripts walking directory trees that contain self-referential links; test fixtures that create circular links deliberately; migration artifacts where a link and its target were swapped.","solutions":["Lstat the link instead: construct Stat with dereference=false, or use fs.getFileLinkStatus(p) which does not follow links","Find and break the cycle: walk getFileLinkStatus until revisiting a target, then delete or repoint one link","Guard tree walks against revisiting already-seen symlink targets"],"exampleFix":"// before\nFileStatus st = new Stat(path, true, shell).getFileStatus(); // follows links, ELOOP\n\n// after\nFileStatus st = new Stat(path, false, shell).getFileStatus(); // lstat semantics","handlingStrategy":"validation","validationCode":"// do not follow links when a cycle is possible\nboolean dereference = false;\nFileStatus st = new Stat(path, dereference, shell).getFileStatus();\n// or link-only status: fs.getFileLinkStatus(p)","typeGuard":null,"tryCatchPattern":"try {\n  st = new Stat(path, true, shell).getFileStatus();\n} catch (IOException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"cyclic\")) {\n    // ELOOP detected: switch to lstat and break the loop\n    st = new Stat(path, false, shell).getFileStatus();\n  } else {\n    throw e;\n  }\n}","preventionTips":["Default to dereference=false unless you must resolve the final target","Track visited symlink targets in tree walks and stop when a target repeats"],"tags":["hadoop","stat","symlink","eloop"],"backgroundTag":"symlink-loop","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}