1111
1212import org .elasticsearch .core .Nullable ;
1313import org .elasticsearch .core .Strings ;
14+ import org .elasticsearch .core .SuppressForbidden ;
1415import org .elasticsearch .entitlement .runtime .policy .entitlements .FilesEntitlement ;
1516import org .elasticsearch .entitlement .runtime .policy .entitlements .FilesEntitlement .Mode ;
1617import org .elasticsearch .logging .LogManager ;
1718import org .elasticsearch .logging .Logger ;
1819
20+ import java .io .File ;
1921import java .io .IOException ;
2022import java .io .UncheckedIOException ;
2123import java .nio .file .Files ;
3335
3436import static java .util .Comparator .comparing ;
3537import static org .elasticsearch .core .PathUtils .getDefaultFileSystem ;
36- import static org .elasticsearch .entitlement .runtime .policy .FileUtils .PATH_ORDER ;
3738import static org .elasticsearch .entitlement .runtime .policy .PathLookup .BaseDir .CONFIG ;
3839import static org .elasticsearch .entitlement .runtime .policy .PathLookup .BaseDir .TEMP ;
3940import static org .elasticsearch .entitlement .runtime .policy .entitlements .FilesEntitlement .Mode .READ_WRITE ;
7071 * Secondly, the path separator (whether slash or backslash) sorts after the dot character. This means, for example, if the array contains
7172 * {@code ["/a", "/a.xml"]} and we look up {@code "/a/b"} using normal string comparison, the binary search would land on {@code "/a.xml"},
7273 * which is neither an exact match nor a containing directory, and so the lookup would incorrectly report no match, even though
73- * {@code "/a"} is in the array. To fix this, we define {@link FileUtils#PATH_ORDER } which sorts path separators before any other
74- * character. In the example, this would cause {@code "/a/b"} to sort between {@code "/a"} and {@code "/a.xml"} so that it correctly
75- * finds {@code "/a"}.
74+ * {@code "/a"} is in the array. To fix this, we define {@link FileAccessTreeComparison#pathComparator() } which sorts path separators
75+ * before any other character. In the example, this would cause {@code "/a/b"} to sort between {@code "/a"} and {@code "/a.xml"} so that
76+ * it correctly finds {@code "/a"}.
7677 * </p>
7778 * With the paths pruned, sorted, and segregated by permission, each binary search has the following properties:
7879 * <ul>
@@ -118,7 +119,11 @@ public String toString() {
118119 }
119120 }
120121
121- static List <ExclusivePath > buildExclusivePathList (List <ExclusiveFileEntitlement > exclusiveFileEntitlements , PathLookup pathLookup ) {
122+ static List <ExclusivePath > buildExclusivePathList (
123+ List <ExclusiveFileEntitlement > exclusiveFileEntitlements ,
124+ PathLookup pathLookup ,
125+ FileAccessTreeComparison comparison
126+ ) {
122127 Map <String , ExclusivePath > exclusivePaths = new HashMap <>();
123128 for (ExclusiveFileEntitlement efe : exclusiveFileEntitlements ) {
124129 for (FilesEntitlement .FileData fd : efe .filesEntitlement ().filesData ()) {
@@ -150,15 +155,16 @@ static List<ExclusivePath> buildExclusivePathList(List<ExclusiveFileEntitlement>
150155 }
151156 }
152157 }
153- return exclusivePaths .values ().stream ().sorted (comparing (ExclusivePath ::path , PATH_ORDER )).distinct ().toList ();
158+ return exclusivePaths .values ().stream ().sorted (comparing (ExclusivePath ::path , comparison . pathComparator () )).distinct ().toList ();
154159 }
155160
156- static void validateExclusivePaths (List <ExclusivePath > exclusivePaths ) {
161+ static void validateExclusivePaths (List <ExclusivePath > exclusivePaths , FileAccessTreeComparison comparison ) {
157162 if (exclusivePaths .isEmpty () == false ) {
158163 ExclusivePath currentExclusivePath = exclusivePaths .get (0 );
159164 for (int i = 1 ; i < exclusivePaths .size (); ++i ) {
160165 ExclusivePath nextPath = exclusivePaths .get (i );
161- if (currentExclusivePath .path ().equals (nextPath .path ) || isParent (currentExclusivePath .path (), nextPath .path ())) {
166+ if (comparison .samePath (currentExclusivePath .path (), nextPath .path )
167+ || comparison .isParent (currentExclusivePath .path (), nextPath .path ())) {
162168 throw new IllegalArgumentException (
163169 "duplicate/overlapping exclusive paths found in files entitlements: " + currentExclusivePath + " and " + nextPath
164170 );
@@ -168,9 +174,18 @@ static void validateExclusivePaths(List<ExclusivePath> exclusivePaths) {
168174 }
169175 }
170176
177+ @ SuppressForbidden (reason = "we need the separator as a char, not a string" )
178+ static char separatorChar () {
179+ return File .separatorChar ;
180+ }
181+
171182 private static final Logger logger = LogManager .getLogger (FileAccessTree .class );
172183 private static final String FILE_SEPARATOR = getDefaultFileSystem ().getSeparator ();
184+ static final FileAccessTreeComparison DEFAULT_COMPARISON = Platform .LINUX .isCurrent ()
185+ ? new CaseSensitiveComparison (separatorChar ())
186+ : new CaseInsensitiveComparison (separatorChar ());
173187
188+ private final FileAccessTreeComparison comparison ;
174189 /**
175190 * lists paths that are forbidden for this component+module because some other component has granted exclusive access to one of its
176191 * modules
@@ -188,19 +203,27 @@ static void validateExclusivePaths(List<ExclusivePath> exclusivePaths) {
188203 private static String [] buildUpdatedAndSortedExclusivePaths (
189204 String componentName ,
190205 String moduleName ,
191- List <ExclusivePath > exclusivePaths
206+ List <ExclusivePath > exclusivePaths ,
207+ FileAccessTreeComparison comparison
192208 ) {
193209 List <String > updatedExclusivePaths = new ArrayList <>();
194210 for (ExclusivePath exclusivePath : exclusivePaths ) {
195211 if (exclusivePath .componentName ().equals (componentName ) == false || exclusivePath .moduleNames ().contains (moduleName ) == false ) {
196212 updatedExclusivePaths .add (exclusivePath .path ());
197213 }
198214 }
199- updatedExclusivePaths .sort (PATH_ORDER );
215+ updatedExclusivePaths .sort (comparison . pathComparator () );
200216 return updatedExclusivePaths .toArray (new String [0 ]);
201217 }
202218
203- private FileAccessTree (FilesEntitlement filesEntitlement , PathLookup pathLookup , Path componentPath , String [] sortedExclusivePaths ) {
219+ FileAccessTree (
220+ FilesEntitlement filesEntitlement ,
221+ PathLookup pathLookup ,
222+ Path componentPath ,
223+ String [] sortedExclusivePaths ,
224+ FileAccessTreeComparison comparison
225+ ) {
226+ this .comparison = comparison ;
204227 List <String > readPaths = new ArrayList <>();
205228 List <String > writePaths = new ArrayList <>();
206229 BiConsumer <Path , Mode > addPath = (path , mode ) -> {
@@ -252,12 +275,12 @@ private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup,
252275 Path jdk = Paths .get (System .getProperty ("java.home" ));
253276 addPathAndMaybeLink .accept (jdk .resolve ("conf" ), Mode .READ );
254277
255- readPaths .sort (PATH_ORDER );
256- writePaths .sort (PATH_ORDER );
278+ readPaths .sort (comparison . pathComparator () );
279+ writePaths .sort (comparison . pathComparator () );
257280
258281 this .exclusivePaths = sortedExclusivePaths ;
259- this .readPaths = pruneSortedPaths (readPaths ).toArray (new String [0 ]);
260- this .writePaths = pruneSortedPaths (writePaths ).toArray (new String [0 ]);
282+ this .readPaths = pruneSortedPaths (readPaths , comparison ).toArray (new String [0 ]);
283+ this .writePaths = pruneSortedPaths (writePaths , comparison ).toArray (new String [0 ]);
261284
262285 logger .debug (
263286 () -> Strings .format (
@@ -270,14 +293,14 @@ private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup,
270293 }
271294
272295 // package private for testing
273- static List <String > pruneSortedPaths (List <String > paths ) {
296+ static List <String > pruneSortedPaths (List <String > paths , FileAccessTreeComparison comparison ) {
274297 List <String > prunedReadPaths = new ArrayList <>();
275298 if (paths .isEmpty () == false ) {
276299 String currentPath = paths .get (0 );
277300 prunedReadPaths .add (currentPath );
278301 for (int i = 1 ; i < paths .size (); ++i ) {
279302 String nextPath = paths .get (i );
280- if (currentPath . equals ( nextPath ) == false && isParent (currentPath , nextPath ) == false ) {
303+ if (comparison . samePath ( currentPath , nextPath ) == false && comparison . isParent (currentPath , nextPath ) == false ) {
281304 prunedReadPaths .add (nextPath );
282305 currentPath = nextPath ;
283306 }
@@ -298,7 +321,8 @@ static FileAccessTree of(
298321 filesEntitlement ,
299322 pathLookup ,
300323 componentPath ,
301- buildUpdatedAndSortedExclusivePaths (componentName , moduleName , exclusivePaths )
324+ buildUpdatedAndSortedExclusivePaths (componentName , moduleName , exclusivePaths , DEFAULT_COMPARISON ),
325+ DEFAULT_COMPARISON
302326 );
303327 }
304328
@@ -310,7 +334,7 @@ public static FileAccessTree withoutExclusivePaths(
310334 PathLookup pathLookup ,
311335 @ Nullable Path componentPath
312336 ) {
313- return new FileAccessTree (filesEntitlement , pathLookup , componentPath , new String [0 ]);
337+ return new FileAccessTree (filesEntitlement , pathLookup , componentPath , new String [0 ], DEFAULT_COMPARISON );
314338 }
315339
316340 public boolean canRead (Path path ) {
@@ -346,24 +370,18 @@ private boolean checkPath(String path, String[] paths) {
346370 return false ;
347371 }
348372
349- int endx = Arrays .binarySearch (exclusivePaths , path , PATH_ORDER );
350- if (endx < -1 && isParent (exclusivePaths [-endx - 2 ], path ) || endx >= 0 ) {
373+ int endx = Arrays .binarySearch (exclusivePaths , path , comparison . pathComparator () );
374+ if (endx < -1 && comparison . isParent (exclusivePaths [-endx - 2 ], path ) || endx >= 0 ) {
351375 return false ;
352376 }
353377
354- int ndx = Arrays .binarySearch (paths , path , PATH_ORDER );
378+ int ndx = Arrays .binarySearch (paths , path , comparison . pathComparator () );
355379 if (ndx < -1 ) {
356- return isParent (paths [-ndx - 2 ], path );
380+ return comparison . isParent (paths [-ndx - 2 ], path );
357381 }
358382 return ndx >= 0 ;
359383 }
360384
361- private static boolean isParent (String maybeParent , String path ) {
362- var isParent = path .startsWith (maybeParent ) && path .startsWith (FILE_SEPARATOR , maybeParent .length ());
363- logger .trace (() -> Strings .format ("checking isParent [%s] for [%s]: %b" , maybeParent , path , isParent ));
364- return isParent ;
365- }
366-
367385 @ Override
368386 public boolean equals (Object o ) {
369387 if (o == null || getClass () != o .getClass ()) return false ;
0 commit comments