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 ;
@@ -110,6 +114,9 @@ public class IndexDatabase {
110
114
111
115
private final Object INSTANCE_LOCK = new Object ();
112
116
117
+ /** Key is canonical path; Value is the first accepted, absolute path. */
118
+ private final Map <String , String > acceptedNonlocalSymlinks = new HashMap <>();
119
+
113
120
private Project project ;
114
121
private FSDirectory indexDirectory ;
115
122
private IndexReader reader ;
@@ -390,6 +397,7 @@ public void update(IndexerParallelizer parallelizer)
390
397
writer = null ;
391
398
settings = null ;
392
399
uidIter = null ;
400
+ acceptedNonlocalSymlinks .clear ();
393
401
394
402
IOException finishingException = null ;
395
403
try {
@@ -775,9 +783,15 @@ private static void cleanupResources(Document doc) {
775
783
* Check if I should accept this file into the index database
776
784
*
777
785
* @param file the file to check
786
+ * @param outLocalRelPath optional output array whose 0-th element is set
787
+ * to a relative path if and only if the {@code file} is a symlink targeting
788
+ * a local directory (N.b. method will return {@code false})
778
789
* @return true if the file should be included, false otherwise
779
790
*/
780
- private boolean accept (File file ) {
791
+ private boolean accept (File file , String [] outLocalRelPath ) {
792
+ if (outLocalRelPath != null ) {
793
+ outLocalRelPath [0 ] = null ;
794
+ }
781
795
782
796
String absolutePath = file .getAbsolutePath ();
783
797
@@ -799,13 +813,16 @@ private boolean accept(File file) {
799
813
}
800
814
801
815
try {
802
- String canonicalPath = file .getCanonicalPath ();
803
- if (!absolutePath .equals (canonicalPath )
804
- && !acceptSymlink (absolutePath , canonicalPath )) {
805
-
806
- LOGGER .log (Level .FINE , "Skipped symlink ''{0}'' -> ''{1}''" ,
807
- new Object []{absolutePath , canonicalPath });
808
- return false ;
816
+ Path absolute = Paths .get (absolutePath );
817
+ if (Files .isSymbolicLink (absolute )) {
818
+ File canonical = file .getCanonicalFile ();
819
+ if (!absolutePath .equals (canonical .getPath ())
820
+ && !acceptSymlink (absolute , canonical ,
821
+ outLocalRelPath )) {
822
+ LOGGER .log (Level .FINE , "Skipped symlink ''{0}'' -> ''{1}''" ,
823
+ new Object []{absolutePath , canonical });
824
+ return false ;
825
+ }
809
826
}
810
827
//below will only let go files and directories, anything else is considered special and is not added
811
828
if (!file .isFile () && !file .isDirectory ()) {
@@ -839,7 +856,20 @@ private boolean accept(File file) {
839
856
return res ;
840
857
}
841
858
842
- private boolean accept (File parent , File file ) {
859
+ /**
860
+ * Determines if {@code file} should be accepted into the index database.
861
+ * @param parent parent of {@code file}
862
+ * @param file directory object under consideration
863
+ * @param outLocalRelPath optional output array whose 0-th element is set
864
+ * to a relative path if and only if the {@code file} is a symlink targeting
865
+ * a local directory (N.b. method will return {@code false})
866
+ * @return {@code true} if the file should be included; else {@code false}
867
+ */
868
+ private boolean accept (File parent , File file , String [] outLocalRelPath ) {
869
+ if (outLocalRelPath != null ) {
870
+ outLocalRelPath [0 ] = null ;
871
+ }
872
+
843
873
try {
844
874
File f1 = parent .getCanonicalFile ();
845
875
File f2 = file .getCanonicalFile ();
@@ -859,7 +889,7 @@ private boolean accept(File parent, File file) {
859
889
}
860
890
}
861
891
862
- return accept (file );
892
+ return accept (file , outLocalRelPath );
863
893
} catch (IOException ex ) {
864
894
LOGGER .log (Level .WARNING , "Failed to resolve name: {0} {1}" ,
865
895
new Object []{parent .getAbsolutePath (), file .getAbsolutePath ()});
@@ -870,21 +900,53 @@ private boolean accept(File parent, File file) {
870
900
/**
871
901
* Check if I should accept the path containing a symlink
872
902
*
873
- * @param absolutePath the path with a symlink to check
874
- * @param canonicalPath the canonical path to the file
875
- * @return true if the file should be accepted, false otherwise
903
+ * @param absolute the path with a symlink to check
904
+ * @param canonical the canonical file object
905
+ * @param outLocalRelPath optional output array whose 0-th element is set
906
+ * to a relative path if and only if {@code absolute} is a symlink targeting
907
+ * a local directory, {@code canonical} (N.b. method will return
908
+ * {@code false})
909
+ * @return {@code true} if the file should be accepted; else {@code false}
876
910
*/
877
- private boolean acceptSymlink (String absolutePath , String canonicalPath ) throws IOException {
878
- // Always accept local symlinks
879
- if (isLocal (canonicalPath )) {
880
- return true ;
911
+ private boolean acceptSymlink (Path absolute , File canonical ,
912
+ String [] outLocalRelPath ) throws IOException {
913
+ if (outLocalRelPath != null ) {
914
+ outLocalRelPath [0 ] = null ;
915
+ }
916
+
917
+ if (isLocal (canonical .getPath ())) {
918
+ if (!canonical .isDirectory ()) {
919
+ // Always accept symlinks to local non-directories.
920
+ return true ;
921
+ } else {
922
+ /**
923
+ * Do not accept symlinks to local directories, because the
924
+ * canonical target will be indexed on its own -- but
925
+ * relativize() a path to be returned in outLocalRelPath so that
926
+ * a symlink can be replicated in xref/.
927
+ **/
928
+ if (outLocalRelPath != null ) {
929
+ outLocalRelPath [0 ] = absolute .getParent ().relativize (
930
+ canonical .toPath ()).toString ();
931
+ }
932
+ return false ;
933
+ }
934
+ }
935
+
936
+ // No need to synchronize, as indexDown() runs on one thread.
937
+ if (acceptedNonlocalSymlinks .containsKey (canonical .getPath ())) {
938
+ return false ;
881
939
}
882
940
941
+ String absolstr = absolute .toString ();
883
942
for (String allowedSymlink : RuntimeEnvironment .getInstance ().getAllowedSymlinks ()) {
884
- if (absolutePath .startsWith (allowedSymlink )) {
943
+ if (absolstr .startsWith (allowedSymlink )) {
885
944
String allowedTarget = new File (allowedSymlink ).getCanonicalPath ();
886
- if (canonicalPath .startsWith (allowedTarget )
887
- && absolutePath .substring (allowedSymlink .length ()).equals (canonicalPath .substring (allowedTarget .length ()))) {
945
+ String canonstr = canonical .getPath ();
946
+ if (canonstr .startsWith (allowedTarget )
947
+ && absolstr .substring (allowedSymlink .length ()).equals (
948
+ canonstr .substring (allowedTarget .length ()))) {
949
+ acceptedNonlocalSymlinks .put (canonstr , absolstr );
888
950
return true ;
889
951
}
890
952
}
@@ -949,7 +1011,20 @@ private void indexDown(File dir, String parent, IndexDownArgs args)
949
1011
return ;
950
1012
}
951
1013
952
- if (!accept (dir )) {
1014
+ String [] outLocalRelPath = new String [1 ];
1015
+ if (!accept (dir , outLocalRelPath )) {
1016
+ /**
1017
+ * If outLocalRelPath[0] is defined, then a local symlink was
1018
+ * detected but not "accepted" to avoid redundancy with its
1019
+ * canonical target. Set up for a deferred recreation of the symlink
1020
+ * for xref/.
1021
+ */
1022
+ if (outLocalRelPath [0 ] != null ) {
1023
+ File xrefPath = new File (xrefDir , parent );
1024
+ PendingSymlinkage psym = new PendingSymlinkage (
1025
+ xrefPath .getAbsolutePath (), outLocalRelPath [0 ]);
1026
+ completer .add (psym );
1027
+ }
953
1028
return ;
954
1029
}
955
1030
@@ -962,9 +1037,15 @@ private void indexDown(File dir, String parent, IndexDownArgs args)
962
1037
Arrays .sort (files , FILENAME_COMPARATOR );
963
1038
964
1039
for (File file : files ) {
965
- if (accept (dir , file )) {
966
- String path = parent + '/' + file .getName ();
967
-
1040
+ String path = parent + '/' + file .getName ();
1041
+ if (!accept (dir , file , outLocalRelPath )) {
1042
+ if (outLocalRelPath [0 ] != null ) {
1043
+ File xrefPath = new File (xrefDir , path );
1044
+ PendingSymlinkage psym = new PendingSymlinkage (
1045
+ xrefPath .getAbsolutePath (), outLocalRelPath [0 ]);
1046
+ completer .add (psym );
1047
+ }
1048
+ } else {
968
1049
if (file .isDirectory ()) {
969
1050
indexDown (file , path , args );
970
1051
} else {
@@ -1510,7 +1591,7 @@ private void finishWriting() throws IOException {
1510
1591
hasPendingCommit = true ;
1511
1592
1512
1593
int n = completer .complete ();
1513
- LOGGER .log (Level .FINE , "completed {0} file (s)" , n );
1594
+ LOGGER .log (Level .FINE , "completed {0} object (s)" , n );
1514
1595
1515
1596
// Just before commit(), reset the `hasPendingCommit' flag,
1516
1597
// since after commit() is called, there is no need for
0 commit comments