Skip to content

Commit fbe755c

Browse files
committed
Fix #2594 Fix #2913 : acceptSymlink() fix and improvement
- Properly check acceptedNonlocalSymlinks. - Add logging. - Simplify check of accepted symlinks not to bother doing startsWith() checks when the net effect is to test equal() to a canonical target.
1 parent 3530bfb commit fbe755c

File tree

1 file changed

+109
-67
lines changed

1 file changed

+109
-67
lines changed

opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDatabase.java

Lines changed: 109 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
import org.opengrok.indexer.configuration.RuntimeEnvironment;
9595
import org.opengrok.indexer.history.HistoryException;
9696
import org.opengrok.indexer.history.HistoryGuru;
97+
import org.opengrok.indexer.history.Repository;
9798
import org.opengrok.indexer.logger.LoggerFactory;
9899
import org.opengrok.indexer.search.QueryBuilder;
99100
import org.opengrok.indexer.util.ForbiddenSymlinkException;
@@ -816,16 +817,15 @@ private static void cleanupResources(Document doc) {
816817
* Check if I should accept this file into the index database.
817818
*
818819
* @param file the file to check
819-
* @param outLocalRelPath optional output array whose 0-th element is set
820-
* to a relative path if and only if the {@code file} is a symlink targeting
821-
* a local directory (N.b. method will return {@code false})
822-
* @return true if the file should be included, false otherwise
820+
* @param ret defined instance whose {@code localRelPath} property will be
821+
* non-null afterward if and only if {@code file} is a symlink that targets
822+
* either a {@link Repository}-local filesystem object or the same object
823+
* as a previously-detected and allowed symlink. N.b. method will return
824+
* {@code false} if {@code ret.localRelPath} is set non-null.
825+
* @return a value indicating if {@code file} should be included in index
823826
*/
824-
private boolean accept(File file, String[] outLocalRelPath) {
825-
if (outLocalRelPath != null) {
826-
outLocalRelPath[0] = null;
827-
}
828-
827+
private boolean accept(File file, AcceptSymlinkRet ret) {
828+
ret.localRelPath = null;
829829
String absolutePath = file.getAbsolutePath();
830830

831831
if (!includedNames.isEmpty()
@@ -849,9 +849,8 @@ private boolean accept(File file, String[] outLocalRelPath) {
849849
Path absolute = Paths.get(absolutePath);
850850
if (Files.isSymbolicLink(absolute)) {
851851
File canonical = file.getCanonicalFile();
852-
if (!absolutePath.equals(canonical.getPath())
853-
&& !acceptSymlink(absolute, canonical,
854-
outLocalRelPath)) {
852+
if (!absolutePath.equals(canonical.getPath()) &&
853+
!acceptSymlink(absolute, canonical, ret)) {
855854
LOGGER.log(Level.FINE, "Skipped symlink ''{0}'' -> ''{1}''",
856855
new Object[]{absolutePath, canonical});
857856
return false;
@@ -893,15 +892,15 @@ private boolean accept(File file, String[] outLocalRelPath) {
893892
* Determines if {@code file} should be accepted into the index database.
894893
* @param parent parent of {@code file}
895894
* @param file directory object under consideration
896-
* @param outLocalRelPath optional output array whose 0-th element is set
897-
* to a relative path if and only if the {@code file} is a symlink targeting
898-
* a local directory (N.b. method will return {@code false})
899-
* @return {@code true} if the file should be included; else {@code false}
895+
* @param ret defined instance whose {@code localRelPath} property will be
896+
* non-null afterward if and only if {@code file} is a symlink that targets
897+
* either a {@link Repository}-local filesystem object or the same object
898+
* as a previously-detected and allowed symlink. N.b. method will return
899+
* {@code false} if {@code ret.localRelPath} is set non-null.
900+
* @return a value indicating if {@code file} should be included in index
900901
*/
901-
private boolean accept(File parent, File file, String[] outLocalRelPath) {
902-
if (outLocalRelPath != null) {
903-
outLocalRelPath[0] = null;
904-
}
902+
private boolean accept(File parent, File file, AcceptSymlinkRet ret) {
903+
ret.localRelPath = null;
905904

906905
try {
907906
File f1 = parent.getCanonicalFile();
@@ -922,7 +921,7 @@ private boolean accept(File parent, File file, String[] outLocalRelPath) {
922921
}
923922
}
924923

925-
return accept(file, outLocalRelPath);
924+
return accept(file, ret);
926925
} catch (IOException ex) {
927926
LOGGER.log(Level.WARNING, "Failed to resolve name: {0} {1}",
928927
new Object[]{parent.getAbsolutePath(), file.getAbsolutePath()});
@@ -935,53 +934,91 @@ private boolean accept(File parent, File file, String[] outLocalRelPath) {
935934
*
936935
* @param absolute the path with a symlink to check
937936
* @param canonical the canonical file object
938-
* @param outLocalRelPath optional output array whose 0-th element is set
939-
* to a relative path if and only if {@code absolute} is a symlink targeting
940-
* a local directory, {@code canonical} (N.b. method will return
941-
* {@code false})
942-
* @return {@code true} if the file should be accepted; else {@code false}
937+
* @param ret defined instance whose {@code localRelPath} property will be
938+
* non-null afterward if and only if {@code absolute} is a symlink that
939+
* targets either a {@link Repository}-local filesystem object or the same
940+
* object ({@code canonical}) as a previously-detected and allowed symlink.
941+
* N.b. method will return {@code false} if {@code ret.localRelPath} is set
942+
* non-null.
943+
* @return a value indicating if {@code file} should be included in index
943944
*/
944-
private boolean acceptSymlink(Path absolute, File canonical,
945-
String[] outLocalRelPath) throws IOException {
946-
if (outLocalRelPath != null) {
947-
outLocalRelPath[0] = null;
948-
}
949-
950-
if (isLocal(canonical.getPath())) {
951-
if (!canonical.isDirectory()) {
952-
// Always accept symlinks to local non-directories.
945+
private boolean acceptSymlink(Path absolute, File canonical, AcceptSymlinkRet ret) {
946+
ret.localRelPath = null;
947+
948+
String absolute1 = absolute.toString();
949+
String canonical1 = canonical.getPath();
950+
boolean isCanonicalDir = canonical.isDirectory();
951+
952+
if (isLocal(canonical1)) {
953+
if (!isCanonicalDir) {
954+
// Always accept symlinks to local files.
955+
if (LOGGER.isLoggable(Level.FINEST)) {
956+
LOGGER.log(Level.FINEST, "Local {0} has symlink from {1}",
957+
new Object[] {canonical1, absolute1});
958+
}
953959
return true;
954960
} else {
955-
/**
961+
/*
956962
* Do not accept symlinks to local directories, because the
957963
* canonical target will be indexed on its own -- but
958-
* relativize() a path to be returned in outLocalRelPath so that
964+
* relativize() a path to be returned in ret so that
959965
* a symlink can be replicated in xref/.
960-
**/
961-
if (outLocalRelPath != null) {
962-
outLocalRelPath[0] = absolute.getParent().relativize(
963-
canonical.toPath()).toString();
964-
}
966+
*/
967+
ret.localRelPath = absolute.getParent().relativize(
968+
canonical.toPath()).toString();
965969
return false;
966970
}
967971
}
968972

973+
String absolute0;
969974
// No need to synchronize, as indexDown() runs on one thread.
970-
if (acceptedNonlocalSymlinks.containsKey(canonical.getPath())) {
971-
return false;
972-
}
975+
if ((absolute0 = acceptedNonlocalSymlinks.get(canonical1)) != null) {
976+
if (absolute1.equals(absolute0)) {
977+
return true;
978+
} else if (!isCanonicalDir) {
979+
if (LOGGER.isLoggable(Level.FINE)) {
980+
LOGGER.log(Level.FINE, "External file {0} has symlink from {1} after first {2}",
981+
new Object[] {canonical1, absolute1, absolute0});
982+
}
983+
return true;
984+
} else {
985+
/*
986+
* Do not accept symlinks to external directories already
987+
* accepted as linked elsewhere, because the canonical target
988+
* will be indexed already -- but relativize() a path to be
989+
* returned in ret so that this second symlink can be redone as
990+
* a local (non-external) symlink in xref/.
991+
*/
992+
ret.localRelPath = absolute.getParent().relativize(
993+
Paths.get(absolute0)).toString();
973994

974-
String absolstr = absolute.toString();
975-
for (String allowedSymlink : RuntimeEnvironment.getInstance().getAllowedSymlinks()) {
976-
if (absolstr.startsWith(allowedSymlink)) {
977-
String allowedTarget = new File(allowedSymlink).getCanonicalPath();
978-
String canonstr = canonical.getPath();
979-
if (canonstr.startsWith(allowedTarget)
980-
&& absolstr.substring(allowedSymlink.length()).equals(
981-
canonstr.substring(allowedTarget.length()))) {
982-
acceptedNonlocalSymlinks.put(canonstr, absolstr);
983-
return true;
995+
if (LOGGER.isLoggable(Level.FINE)) {
996+
LOGGER.log(Level.FINE, "External dir {0} has symlink from {1} after first {2}",
997+
new Object[] {canonical1, absolute1, absolute0});
984998
}
999+
return false;
1000+
}
1001+
}
1002+
1003+
Set<String> allowedSymlinks = RuntimeEnvironment.getInstance().getAllowedSymlinks();
1004+
for (String allowedSymlink : allowedSymlinks) {
1005+
String allowedTarget;
1006+
try {
1007+
allowedTarget = new File(allowedSymlink).getCanonicalPath();
1008+
} catch (IOException e) {
1009+
LOGGER.log(Level.FINE, "unresolvable symlink: {0}", allowedSymlink);
1010+
continue;
1011+
}
1012+
/*
1013+
* The following canonical check is sufficient because indexDown()
1014+
* traverses top-down, and any intermediate symlinks would have
1015+
* also been checked here for an allowed canonical match. This
1016+
* technically means that if there is a set of redundant symlinks
1017+
* with the same canonical target, then allowing one of the set
1018+
* will allow all others in the set.
1019+
*/
1020+
if (canonical1.equals(allowedTarget)) {
1021+
return true;
9851022
}
9861023
}
9871024
return false;
@@ -1035,18 +1072,18 @@ private void indexDown(File dir, String parent, IndexDownArgs args)
10351072
return;
10361073
}
10371074

1038-
String[] outLocalRelPath = new String[1];
1039-
if (!accept(dir, outLocalRelPath)) {
1040-
/**
1041-
* If outLocalRelPath[0] is defined, then a local symlink was
1042-
* detected but not "accepted" to avoid redundancy with its
1043-
* canonical target. Set up for a deferred recreation of the symlink
1044-
* for xref/.
1075+
AcceptSymlinkRet ret = new AcceptSymlinkRet();
1076+
if (!accept(dir, ret)) {
1077+
/*
1078+
* If ret.localRelPath is defined, then a symlink was detected but
1079+
* not "accepted" to avoid redundancy with an already-accepted
1080+
* canonical target. Set up for a deferred creation of a symlink
1081+
* within xref/.
10451082
*/
1046-
if (outLocalRelPath[0] != null) {
1083+
if (ret.localRelPath != null) {
10471084
File xrefPath = new File(xrefDir, parent);
10481085
PendingSymlinkage psym = new PendingSymlinkage(
1049-
xrefPath.getAbsolutePath(), outLocalRelPath[0]);
1086+
xrefPath.getAbsolutePath(), ret.localRelPath);
10501087
completer.add(psym);
10511088
}
10521089
return;
@@ -1062,11 +1099,12 @@ private void indexDown(File dir, String parent, IndexDownArgs args)
10621099

10631100
for (File file : files) {
10641101
String path = parent + File.separator + file.getName();
1065-
if (!accept(dir, file, outLocalRelPath)) {
1066-
if (outLocalRelPath[0] != null) {
1102+
if (!accept(dir, file, ret)) {
1103+
if (ret.localRelPath != null) {
1104+
// See note above about ret.localRelPath.
10671105
File xrefPath = new File(xrefDir, path);
10681106
PendingSymlinkage psym = new PendingSymlinkage(
1069-
xrefPath.getAbsolutePath(), outLocalRelPath[0]);
1107+
xrefPath.getAbsolutePath(), ret.localRelPath);
10701108
completer.add(psym);
10711109
}
10721110
} else {
@@ -1767,4 +1805,8 @@ private static class IndexFileWork {
17671805
this.path = path;
17681806
}
17691807
}
1808+
1809+
private static class AcceptSymlinkRet {
1810+
String localRelPath;
1811+
}
17701812
}

0 commit comments

Comments
 (0)