34
34
import java .io .OutputStreamWriter ;
35
35
import java .io .Writer ;
36
36
import java .net .ConnectException ;
37
+ import java .nio .file .Files ;
38
+ import java .nio .file .Path ;
39
+ import java .nio .file .Paths ;
37
40
import java .util .ArrayList ;
38
41
import java .util .Arrays ;
39
42
import java .util .Comparator ;
43
+ import java .util .HashMap ;
40
44
import java .util .HashSet ;
41
45
import java .util .List ;
42
46
import java .util .Map ;
@@ -114,6 +118,9 @@ public class IndexDatabase {
114
118
115
119
private final Object INSTANCE_LOCK = new Object ();
116
120
121
+ /** Key is canonical path; Value is the first accepted, absolute path. */
122
+ private final Map <String , String > acceptedNonlocalSymlinks = new HashMap <>();
123
+
117
124
private Project project ;
118
125
private FSDirectory indexDirectory ;
119
126
private IndexReader reader ;
@@ -401,6 +408,7 @@ public void update(IndexerParallelizer parallelizer)
401
408
settings = null ;
402
409
uidIter = null ;
403
410
postsIter = null ;
411
+ acceptedNonlocalSymlinks .clear ();
404
412
405
413
IOException finishingException = null ;
406
414
try {
@@ -792,9 +800,15 @@ private static void cleanupResources(Document doc) {
792
800
* Check if I should accept this file into the index database
793
801
*
794
802
* @param file the file to check
803
+ * @param outLocalRelPath optional output array whose 0-th element is set
804
+ * to a relative path if and only if the {@code file} is a symlink targeting
805
+ * a local directory (N.b. method will return {@code false})
795
806
* @return true if the file should be included, false otherwise
796
807
*/
797
- private boolean accept (File file ) {
808
+ private boolean accept (File file , String [] outLocalRelPath ) {
809
+ if (outLocalRelPath != null ) {
810
+ outLocalRelPath [0 ] = null ;
811
+ }
798
812
799
813
String absolutePath = file .getAbsolutePath ();
800
814
@@ -816,13 +830,16 @@ private boolean accept(File file) {
816
830
}
817
831
818
832
try {
819
- String canonicalPath = file .getCanonicalPath ();
820
- if (!absolutePath .equals (canonicalPath )
821
- && !acceptSymlink (absolutePath , canonicalPath )) {
822
-
823
- LOGGER .log (Level .FINE , "Skipped symlink ''{0}'' -> ''{1}''" ,
824
- new Object []{absolutePath , canonicalPath });
825
- return false ;
833
+ Path absolute = Paths .get (absolutePath );
834
+ if (Files .isSymbolicLink (absolute )) {
835
+ File canonical = file .getCanonicalFile ();
836
+ if (!absolutePath .equals (canonical .getPath ())
837
+ && !acceptSymlink (absolute , canonical ,
838
+ outLocalRelPath )) {
839
+ LOGGER .log (Level .FINE , "Skipped symlink ''{0}'' -> ''{1}''" ,
840
+ new Object []{absolutePath , canonical });
841
+ return false ;
842
+ }
826
843
}
827
844
//below will only let go files and directories, anything else is considered special and is not added
828
845
if (!file .isFile () && !file .isDirectory ()) {
@@ -856,7 +873,20 @@ private boolean accept(File file) {
856
873
return res ;
857
874
}
858
875
859
- private boolean accept (File parent , File file ) {
876
+ /**
877
+ * Determines if {@code file} should be accepted into the index database.
878
+ * @param parent parent of {@code file}
879
+ * @param file directory object under consideration
880
+ * @param outLocalRelPath optional output array whose 0-th element is set
881
+ * to a relative path if and only if the {@code file} is a symlink targeting
882
+ * a local directory (N.b. method will return {@code false})
883
+ * @return {@code true} if the file should be included; else {@code false}
884
+ */
885
+ private boolean accept (File parent , File file , String [] outLocalRelPath ) {
886
+ if (outLocalRelPath != null ) {
887
+ outLocalRelPath [0 ] = null ;
888
+ }
889
+
860
890
try {
861
891
File f1 = parent .getCanonicalFile ();
862
892
File f2 = file .getCanonicalFile ();
@@ -876,7 +906,7 @@ private boolean accept(File parent, File file) {
876
906
}
877
907
}
878
908
879
- return accept (file );
909
+ return accept (file , outLocalRelPath );
880
910
} catch (IOException ex ) {
881
911
LOGGER .log (Level .WARNING , "Failed to resolve name: {0} {1}" ,
882
912
new Object []{parent .getAbsolutePath (), file .getAbsolutePath ()});
@@ -887,21 +917,53 @@ private boolean accept(File parent, File file) {
887
917
/**
888
918
* Check if I should accept the path containing a symlink
889
919
*
890
- * @param absolutePath the path with a symlink to check
891
- * @param canonicalPath the canonical path to the file
892
- * @return true if the file should be accepted, false otherwise
920
+ * @param absolute the path with a symlink to check
921
+ * @param canonical the canonical file object
922
+ * @param outLocalRelPath optional output array whose 0-th element is set
923
+ * to a relative path if and only if {@code absolute} is a symlink targeting
924
+ * a local directory, {@code canonical} (N.b. method will return
925
+ * {@code false})
926
+ * @return {@code true} if the file should be accepted; else {@code false}
893
927
*/
894
- private boolean acceptSymlink (String absolutePath , String canonicalPath ) throws IOException {
895
- // Always accept local symlinks
896
- if (isLocal ( canonicalPath ) ) {
897
- return true ;
928
+ private boolean acceptSymlink (Path absolute , File canonical ,
929
+ String [] outLocalRelPath ) throws IOException {
930
+ if (outLocalRelPath != null ) {
931
+ outLocalRelPath [ 0 ] = null ;
898
932
}
899
933
934
+ if (isLocal (canonical .getPath ())) {
935
+ if (!canonical .isDirectory ()) {
936
+ // Always accept symlinks to local non-directories.
937
+ return true ;
938
+ } else {
939
+ /**
940
+ * Do not accept symlinks to local directories, because the
941
+ * canonical target will be indexed on its own -- but
942
+ * relativize() a path to be returned in outLocalRelPath so that
943
+ * a symlink can be replicated in xref/.
944
+ **/
945
+ if (outLocalRelPath != null ) {
946
+ outLocalRelPath [0 ] = absolute .getParent ().relativize (
947
+ canonical .toPath ()).toString ();
948
+ }
949
+ return false ;
950
+ }
951
+ }
952
+
953
+ // No need to synchronize, as indexDown() runs on one thread.
954
+ if (acceptedNonlocalSymlinks .containsKey (canonical .getPath ())) {
955
+ return false ;
956
+ }
957
+
958
+ String absolstr = absolute .toString ();
900
959
for (String allowedSymlink : RuntimeEnvironment .getInstance ().getAllowedSymlinks ()) {
901
- if (absolutePath .startsWith (allowedSymlink )) {
960
+ if (absolstr .startsWith (allowedSymlink )) {
902
961
String allowedTarget = new File (allowedSymlink ).getCanonicalPath ();
903
- if (canonicalPath .startsWith (allowedTarget )
904
- && absolutePath .substring (allowedSymlink .length ()).equals (canonicalPath .substring (allowedTarget .length ()))) {
962
+ String canonstr = canonical .getPath ();
963
+ if (canonstr .startsWith (allowedTarget )
964
+ && absolstr .substring (allowedSymlink .length ()).equals (
965
+ canonstr .substring (allowedTarget .length ()))) {
966
+ acceptedNonlocalSymlinks .put (canonstr , absolstr );
905
967
return true ;
906
968
}
907
969
}
@@ -966,7 +1028,20 @@ private void indexDown(File dir, String parent, IndexDownArgs args)
966
1028
return ;
967
1029
}
968
1030
969
- if (!accept (dir )) {
1031
+ String [] outLocalRelPath = new String [1 ];
1032
+ if (!accept (dir , outLocalRelPath )) {
1033
+ /**
1034
+ * If outLocalRelPath[0] is defined, then a local symlink was
1035
+ * detected but not "accepted" to avoid redundancy with its
1036
+ * canonical target. Set up for a deferred recreation of the symlink
1037
+ * for xref/.
1038
+ */
1039
+ if (outLocalRelPath [0 ] != null ) {
1040
+ File xrefPath = new File (xrefDir , parent );
1041
+ PendingSymlinkage psym = new PendingSymlinkage (
1042
+ xrefPath .getAbsolutePath (), outLocalRelPath [0 ]);
1043
+ completer .add (psym );
1044
+ }
970
1045
return ;
971
1046
}
972
1047
@@ -979,9 +1054,15 @@ private void indexDown(File dir, String parent, IndexDownArgs args)
979
1054
Arrays .sort (files , FILENAME_COMPARATOR );
980
1055
981
1056
for (File file : files ) {
982
- if (accept (dir , file )) {
983
- String path = parent + '/' + file .getName ();
984
-
1057
+ String path = parent + '/' + file .getName ();
1058
+ if (!accept (dir , file , outLocalRelPath )) {
1059
+ if (outLocalRelPath [0 ] != null ) {
1060
+ File xrefPath = new File (xrefDir , path );
1061
+ PendingSymlinkage psym = new PendingSymlinkage (
1062
+ xrefPath .getAbsolutePath (), outLocalRelPath [0 ]);
1063
+ completer .add (psym );
1064
+ }
1065
+ } else {
985
1066
if (file .isDirectory ()) {
986
1067
indexDown (file , path , args );
987
1068
} else {
@@ -1527,7 +1608,7 @@ private void finishWriting() throws IOException {
1527
1608
hasPendingCommit = true ;
1528
1609
1529
1610
int n = completer .complete ();
1530
- LOGGER .log (Level .FINE , "completed {0} file (s)" , n );
1611
+ LOGGER .log (Level .FINE , "completed {0} object (s)" , n );
1531
1612
1532
1613
// Just before commit(), reset the `hasPendingCommit' flag,
1533
1614
// since after commit() is called, there is no need for
0 commit comments