11
11
12
12
import org .elasticsearch .core .Nullable ;
13
13
import org .elasticsearch .core .Strings ;
14
+ import org .elasticsearch .core .SuppressForbidden ;
14
15
import org .elasticsearch .entitlement .runtime .policy .entitlements .FilesEntitlement ;
15
16
import org .elasticsearch .entitlement .runtime .policy .entitlements .FilesEntitlement .Mode ;
16
17
import org .elasticsearch .logging .LogManager ;
17
18
import org .elasticsearch .logging .Logger ;
18
19
20
+ import java .io .File ;
19
21
import java .io .IOException ;
20
22
import java .io .UncheckedIOException ;
21
23
import java .nio .file .Files ;
33
35
34
36
import static java .util .Comparator .comparing ;
35
37
import static org .elasticsearch .core .PathUtils .getDefaultFileSystem ;
36
- import static org .elasticsearch .entitlement .runtime .policy .FileUtils .PATH_ORDER ;
37
38
import static org .elasticsearch .entitlement .runtime .policy .PathLookup .BaseDir .CONFIG ;
38
39
import static org .elasticsearch .entitlement .runtime .policy .PathLookup .BaseDir .TEMP ;
39
40
import static org .elasticsearch .entitlement .runtime .policy .entitlements .FilesEntitlement .Mode .READ_WRITE ;
70
71
* Secondly, the path separator (whether slash or backslash) sorts after the dot character. This means, for example, if the array contains
71
72
* {@code ["/a", "/a.xml"]} and we look up {@code "/a/b"} using normal string comparison, the binary search would land on {@code "/a.xml"},
72
73
* 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"}.
76
77
* </p>
77
78
* With the paths pruned, sorted, and segregated by permission, each binary search has the following properties:
78
79
* <ul>
@@ -118,7 +119,11 @@ public String toString() {
118
119
}
119
120
}
120
121
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
+ ) {
122
127
Map <String , ExclusivePath > exclusivePaths = new HashMap <>();
123
128
for (ExclusiveFileEntitlement efe : exclusiveFileEntitlements ) {
124
129
for (FilesEntitlement .FileData fd : efe .filesEntitlement ().filesData ()) {
@@ -150,15 +155,16 @@ static List<ExclusivePath> buildExclusivePathList(List<ExclusiveFileEntitlement>
150
155
}
151
156
}
152
157
}
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 ();
154
159
}
155
160
156
- static void validateExclusivePaths (List <ExclusivePath > exclusivePaths ) {
161
+ static void validateExclusivePaths (List <ExclusivePath > exclusivePaths , FileAccessTreeComparison comparison ) {
157
162
if (exclusivePaths .isEmpty () == false ) {
158
163
ExclusivePath currentExclusivePath = exclusivePaths .get (0 );
159
164
for (int i = 1 ; i < exclusivePaths .size (); ++i ) {
160
165
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 ())) {
162
168
throw new IllegalArgumentException (
163
169
"duplicate/overlapping exclusive paths found in files entitlements: " + currentExclusivePath + " and " + nextPath
164
170
);
@@ -168,9 +174,18 @@ static void validateExclusivePaths(List<ExclusivePath> exclusivePaths) {
168
174
}
169
175
}
170
176
177
+ @ SuppressForbidden (reason = "we need the separator as a char, not a string" )
178
+ static char separatorChar () {
179
+ return File .separatorChar ;
180
+ }
181
+
171
182
private static final Logger logger = LogManager .getLogger (FileAccessTree .class );
172
183
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 ());
173
187
188
+ private final FileAccessTreeComparison comparison ;
174
189
/**
175
190
* lists paths that are forbidden for this component+module because some other component has granted exclusive access to one of its
176
191
* modules
@@ -188,19 +203,27 @@ static void validateExclusivePaths(List<ExclusivePath> exclusivePaths) {
188
203
private static String [] buildUpdatedAndSortedExclusivePaths (
189
204
String componentName ,
190
205
String moduleName ,
191
- List <ExclusivePath > exclusivePaths
206
+ List <ExclusivePath > exclusivePaths ,
207
+ FileAccessTreeComparison comparison
192
208
) {
193
209
List <String > updatedExclusivePaths = new ArrayList <>();
194
210
for (ExclusivePath exclusivePath : exclusivePaths ) {
195
211
if (exclusivePath .componentName ().equals (componentName ) == false || exclusivePath .moduleNames ().contains (moduleName ) == false ) {
196
212
updatedExclusivePaths .add (exclusivePath .path ());
197
213
}
198
214
}
199
- updatedExclusivePaths .sort (PATH_ORDER );
215
+ updatedExclusivePaths .sort (comparison . pathComparator () );
200
216
return updatedExclusivePaths .toArray (new String [0 ]);
201
217
}
202
218
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 ;
204
227
List <String > readPaths = new ArrayList <>();
205
228
List <String > writePaths = new ArrayList <>();
206
229
BiConsumer <Path , Mode > addPath = (path , mode ) -> {
@@ -252,12 +275,12 @@ private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup,
252
275
Path jdk = Paths .get (System .getProperty ("java.home" ));
253
276
addPathAndMaybeLink .accept (jdk .resolve ("conf" ), Mode .READ );
254
277
255
- readPaths .sort (PATH_ORDER );
256
- writePaths .sort (PATH_ORDER );
278
+ readPaths .sort (comparison . pathComparator () );
279
+ writePaths .sort (comparison . pathComparator () );
257
280
258
281
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 ]);
261
284
262
285
logger .debug (
263
286
() -> Strings .format (
@@ -270,14 +293,14 @@ private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup,
270
293
}
271
294
272
295
// package private for testing
273
- static List <String > pruneSortedPaths (List <String > paths ) {
296
+ static List <String > pruneSortedPaths (List <String > paths , FileAccessTreeComparison comparison ) {
274
297
List <String > prunedReadPaths = new ArrayList <>();
275
298
if (paths .isEmpty () == false ) {
276
299
String currentPath = paths .get (0 );
277
300
prunedReadPaths .add (currentPath );
278
301
for (int i = 1 ; i < paths .size (); ++i ) {
279
302
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 ) {
281
304
prunedReadPaths .add (nextPath );
282
305
currentPath = nextPath ;
283
306
}
@@ -298,7 +321,8 @@ static FileAccessTree of(
298
321
filesEntitlement ,
299
322
pathLookup ,
300
323
componentPath ,
301
- buildUpdatedAndSortedExclusivePaths (componentName , moduleName , exclusivePaths )
324
+ buildUpdatedAndSortedExclusivePaths (componentName , moduleName , exclusivePaths , DEFAULT_COMPARISON ),
325
+ DEFAULT_COMPARISON
302
326
);
303
327
}
304
328
@@ -310,7 +334,7 @@ public static FileAccessTree withoutExclusivePaths(
310
334
PathLookup pathLookup ,
311
335
@ Nullable Path componentPath
312
336
) {
313
- return new FileAccessTree (filesEntitlement , pathLookup , componentPath , new String [0 ]);
337
+ return new FileAccessTree (filesEntitlement , pathLookup , componentPath , new String [0 ], DEFAULT_COMPARISON );
314
338
}
315
339
316
340
public boolean canRead (Path path ) {
@@ -346,24 +370,18 @@ private boolean checkPath(String path, String[] paths) {
346
370
return false ;
347
371
}
348
372
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 ) {
351
375
return false ;
352
376
}
353
377
354
- int ndx = Arrays .binarySearch (paths , path , PATH_ORDER );
378
+ int ndx = Arrays .binarySearch (paths , path , comparison . pathComparator () );
355
379
if (ndx < -1 ) {
356
- return isParent (paths [-ndx - 2 ], path );
380
+ return comparison . isParent (paths [-ndx - 2 ], path );
357
381
}
358
382
return ndx >= 0 ;
359
383
}
360
384
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
-
367
385
@ Override
368
386
public boolean equals (Object o ) {
369
387
if (o == null || getClass () != o .getClass ()) return false ;
0 commit comments