0
# I/O and File System Virtualization
1
2
The GraalVM Polyglot API provides comprehensive I/O virtualization capabilities, allowing fine-grained control over file system access, network operations, and byte-level data handling. This enables secure execution environments with custom I/O policies and virtual file systems.
3
4
## I/O Access Control
5
6
IOAccess controls the I/O operations that polyglot contexts can perform, providing security boundaries for file system and network access.
7
8
### Predefined IOAccess Policies
9
10
```java { .api }
11
public final class IOAccess {
12
// Full I/O access - all file system and network operations allowed
13
public static final IOAccess ALL;
14
15
// No I/O access - all I/O operations denied
16
public static final IOAccess NONE;
17
}
18
```
19
20
### Custom IOAccess Configuration
21
22
```java { .api }
23
// Factory method for custom I/O configuration
24
public static IOAccess.Builder newBuilder();
25
```
26
27
The IOAccess.Builder provides granular control over I/O operations:
28
29
```java { .api }
30
public static final class IOAccess.Builder {
31
/**
32
* Controls host socket access for network operations.
33
* @param enabled true to allow socket access
34
* @return this builder
35
*/
36
public IOAccess.Builder allowHostSocketAccess(boolean enabled);
37
38
/**
39
* Controls host file access for file system operations.
40
* @param enabled true to allow file access
41
* @return this builder
42
*/
43
public IOAccess.Builder allowHostFileAccess(boolean enabled);
44
45
/**
46
* Sets a custom file system implementation.
47
* @param fileSystem the custom file system
48
* @return this builder
49
*/
50
public IOAccess.Builder fileSystem(FileSystem fileSystem);
51
52
/**
53
* Builds the IOAccess configuration.
54
* @return the configured IOAccess
55
*/
56
public IOAccess build();
57
}
58
```
59
60
**IOAccess Configuration Examples:**
61
62
```java
63
// Allow file access but deny network access
64
IOAccess fileOnlyAccess = IOAccess.newBuilder()
65
.allowHostFileAccess(true)
66
.allowHostSocketAccess(false)
67
.build();
68
69
Context fileContext = Context.newBuilder("js")
70
.allowIO(fileOnlyAccess)
71
.build();
72
73
// Allow network but deny file system
74
IOAccess networkOnlyAccess = IOAccess.newBuilder()
75
.allowHostFileAccess(false)
76
.allowHostSocketAccess(true)
77
.build();
78
79
Context networkContext = Context.newBuilder("js")
80
.allowIO(networkOnlyAccess)
81
.build();
82
83
// Custom file system with restricted access
84
IOAccess customFSAccess = IOAccess.newBuilder()
85
.fileSystem(new RestrictedFileSystem("/allowed/path"))
86
.allowHostSocketAccess(false)
87
.build();
88
89
Context restrictedContext = Context.newBuilder("js")
90
.allowIO(customFSAccess)
91
.build();
92
```
93
94
## Virtual File System Interface
95
96
The FileSystem interface provides a complete abstraction for file system operations, enabling custom file system implementations.
97
98
### FileSystem Interface
99
100
```java { .api }
101
public interface FileSystem {
102
/**
103
* Parses a URI to a Path.
104
* @param uri the URI to parse
105
* @return the path
106
*/
107
Path parsePath(URI uri);
108
109
/**
110
* Parses a string to a Path.
111
* @param path the path string
112
* @return the path
113
*/
114
Path parsePath(String path);
115
116
/**
117
* Checks access permissions for a path.
118
* @param path the path to check
119
* @param modes the access modes to check
120
* @param linkOptions link handling options
121
* @throws IOException if access check fails
122
*/
123
void checkAccess(Path path, Set<? extends AccessMode> modes, LinkOption... linkOptions) throws IOException;
124
125
/**
126
* Creates a directory.
127
* @param dir the directory to create
128
* @param attrs file attributes
129
* @throws IOException if creation fails
130
*/
131
void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException;
132
133
/**
134
* Deletes a file or directory.
135
* @param path the path to delete
136
* @throws IOException if deletion fails
137
*/
138
void delete(Path path) throws IOException;
139
140
/**
141
* Opens a byte channel for reading/writing.
142
* @param path the file path
143
* @param options open options
144
* @param attrs file attributes
145
* @return the byte channel
146
* @throws IOException if opening fails
147
*/
148
SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException;
149
150
/**
151
* Creates a directory stream for listing directory contents.
152
* @param dir the directory
153
* @param filter optional filter for entries
154
* @return directory stream
155
* @throws IOException if listing fails
156
*/
157
DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException;
158
159
/**
160
* Reads file attributes.
161
* @param path the file path
162
* @param attributes attribute pattern to read
163
* @param options link handling options
164
* @return map of attributes
165
* @throws IOException if reading fails
166
*/
167
Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException;
168
169
/**
170
* Sets a file attribute.
171
* @param path the file path
172
* @param attribute the attribute name
173
* @param value the attribute value
174
* @param options link handling options
175
* @throws IOException if setting fails
176
*/
177
void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException;
178
179
/**
180
* Copies a file.
181
* @param source source path
182
* @param target target path
183
* @param options copy options
184
* @throws IOException if copying fails
185
*/
186
void copy(Path source, Path target, CopyOption... options) throws IOException;
187
188
/**
189
* Moves a file.
190
* @param source source path
191
* @param target target path
192
* @param options move options
193
* @throws IOException if moving fails
194
*/
195
void move(Path source, Path target, CopyOption... options) throws IOException;
196
197
/**
198
* Converts to absolute path.
199
* @param path the path to convert
200
* @return absolute path
201
*/
202
Path toAbsolutePath(Path path);
203
204
/**
205
* Resolves to real path (following links).
206
* @param path the path to resolve
207
* @param linkOptions link handling options
208
* @return real path
209
* @throws IOException if resolution fails
210
*/
211
Path toRealPath(Path path, LinkOption... linkOptions) throws IOException;
212
213
/**
214
* Gets the path separator.
215
* @return path separator string
216
*/
217
String getSeparator();
218
219
/**
220
* Gets the path separator for PATH-style variables.
221
* @return path separator string
222
*/
223
String getPathSeparator();
224
225
/**
226
* Detects MIME type of a file.
227
* @param path the file path
228
* @return MIME type or null
229
*/
230
String getMimeType(Path path);
231
232
/**
233
* Detects text encoding of a file.
234
* @param path the file path
235
* @return encoding name or null
236
*/
237
String getEncoding(Path path);
238
239
/**
240
* Gets the temporary directory.
241
* @return temp directory path
242
*/
243
Path getTempDirectory();
244
245
/**
246
* Checks if two paths refer to the same file.
247
* @param path1 first path
248
* @param path2 second path
249
* @param options link handling options
250
* @return true if same file
251
* @throws IOException if comparison fails
252
*/
253
boolean isSameFile(Path path1, Path path2, LinkOption... options) throws IOException;
254
}
255
```
256
257
### Custom FileSystem Implementation Example
258
259
```java
260
import org.graalvm.polyglot.io.FileSystem;
261
import java.nio.file.*;
262
import java.io.IOException;
263
import java.util.*;
264
265
public class RestrictedFileSystem implements FileSystem {
266
private final Path allowedRoot;
267
private final Set<String> allowedExtensions;
268
private final FileSystem delegate;
269
270
public RestrictedFileSystem(String allowedPath) {
271
this.allowedRoot = Paths.get(allowedPath).toAbsolutePath();
272
this.allowedExtensions = Set.of(".txt", ".json", ".csv", ".log");
273
this.delegate = FileSystems.getDefault();
274
}
275
276
@Override
277
public Path parsePath(String path) {
278
Path parsed = delegate.getPath(path);
279
return validatePath(parsed);
280
}
281
282
@Override
283
public Path parsePath(URI uri) {
284
Path parsed = Paths.get(uri);
285
return validatePath(parsed);
286
}
287
288
private Path validatePath(Path path) {
289
Path absolute = path.toAbsolutePath();
290
291
// Ensure path is within allowed root
292
if (!absolute.startsWith(allowedRoot)) {
293
throw new SecurityException("Access denied: path outside allowed directory");
294
}
295
296
// Check file extension
297
String filename = absolute.getFileName().toString();
298
if (filename.contains(".") && allowedExtensions.stream()
299
.noneMatch(filename.toLowerCase()::endsWith)) {
300
throw new SecurityException("Access denied: file type not allowed");
301
}
302
303
return absolute;
304
}
305
306
@Override
307
public void checkAccess(Path path, Set<? extends AccessMode> modes, LinkOption... linkOptions) throws IOException {
308
Path validatedPath = validatePath(path);
309
Files.checkAccess(validatedPath, modes.toArray(new AccessMode[0]));
310
}
311
312
@Override
313
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
314
Path validatedPath = validatePath(path);
315
316
// Log file access
317
System.out.println("File access: " + validatedPath);
318
319
return Files.newByteChannel(validatedPath, options, attrs);
320
}
321
322
@Override
323
public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
324
Path validatedDir = validatePath(dir);
325
326
return Files.newDirectoryStream(validatedDir, entry -> {
327
try {
328
Path validated = validatePath(entry);
329
return filter == null || filter.accept(validated);
330
} catch (SecurityException e) {
331
return false; // Skip inaccessible files
332
} catch (IOException e) {
333
return false;
334
}
335
});
336
}
337
338
@Override
339
public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
340
Path validatedDir = validatePath(dir);
341
Files.createDirectory(validatedDir, attrs);
342
}
343
344
@Override
345
public void delete(Path path) throws IOException {
346
Path validatedPath = validatePath(path);
347
Files.delete(validatedPath);
348
}
349
350
// Additional methods implementation...
351
@Override
352
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
353
Path validatedPath = validatePath(path);
354
return Files.readAttributes(validatedPath, attributes, options);
355
}
356
357
@Override
358
public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
359
Path validatedPath = validatePath(path);
360
Files.setAttribute(validatedPath, attribute, value, options);
361
}
362
363
@Override
364
public void copy(Path source, Path target, CopyOption... options) throws IOException {
365
Path validatedSource = validatePath(source);
366
Path validatedTarget = validatePath(target);
367
Files.copy(validatedSource, validatedTarget, options);
368
}
369
370
@Override
371
public void move(Path source, Path target, CopyOption... options) throws IOException {
372
Path validatedSource = validatePath(source);
373
Path validatedTarget = validatePath(target);
374
Files.move(validatedSource, validatedTarget, options);
375
}
376
377
@Override
378
public Path toAbsolutePath(Path path) {
379
return validatePath(path);
380
}
381
382
@Override
383
public Path toRealPath(Path path, LinkOption... linkOptions) throws IOException {
384
Path validatedPath = validatePath(path);
385
return validatedPath.toRealPath(linkOptions);
386
}
387
388
@Override
389
public String getSeparator() {
390
return delegate.getSeparator();
391
}
392
393
@Override
394
public String getPathSeparator() {
395
return File.pathSeparator;
396
}
397
398
@Override
399
public String getMimeType(Path path) {
400
try {
401
Path validatedPath = validatePath(path);
402
return Files.probeContentType(validatedPath);
403
} catch (Exception e) {
404
return null;
405
}
406
}
407
408
@Override
409
public String getEncoding(Path path) {
410
// Simple encoding detection based on file extension
411
String filename = path.getFileName().toString().toLowerCase();
412
if (filename.endsWith(".json") || filename.endsWith(".js")) {
413
return "UTF-8";
414
}
415
return null;
416
}
417
418
@Override
419
public Path getTempDirectory() {
420
// Return temp directory within allowed root
421
return allowedRoot.resolve("temp");
422
}
423
424
@Override
425
public boolean isSameFile(Path path1, Path path2, LinkOption... options) throws IOException {
426
Path validatedPath1 = validatePath(path1);
427
Path validatedPath2 = validatePath(path2);
428
return Files.isSameFile(validatedPath1, validatedPath2);
429
}
430
}
431
432
// Usage
433
RestrictedFileSystem restrictedFS = new RestrictedFileSystem("/safe/sandbox");
434
Context context = Context.newBuilder("js")
435
.allowIO(IOAccess.newBuilder()
436
.fileSystem(restrictedFS)
437
.allowHostSocketAccess(false)
438
.build())
439
.build();
440
```
441
442
## ByteSequence Interface
443
444
ByteSequence provides an abstraction for byte data that can be used across polyglot boundaries.
445
446
### ByteSequence Interface
447
448
```java { .api }
449
public interface ByteSequence {
450
/**
451
* Returns the length of the byte sequence.
452
* @return the length
453
*/
454
int length();
455
456
/**
457
* Returns the byte at the specified index.
458
* @param index the index
459
* @return the byte value
460
*/
461
byte byteAt(int index);
462
463
/**
464
* Creates a subsequence.
465
* @param startIndex start index (inclusive)
466
* @param endIndex end index (exclusive)
467
* @return the subsequence
468
*/
469
ByteSequence subSequence(int startIndex, int endIndex);
470
471
/**
472
* Converts to byte array.
473
* @return byte array copy
474
*/
475
byte[] toByteArray();
476
}
477
```
478
479
### ByteSequence Implementations
480
481
The polyglot API provides implementations for common use cases:
482
483
**Creating ByteSequence from various sources:**
484
485
```java
486
import org.graalvm.polyglot.io.ByteSequence;
487
488
// From byte array
489
byte[] data = {0x48, 0x65, 0x6C, 0x6C, 0x6F}; // "Hello" in ASCII
490
ByteSequence fromArray = ByteSequence.create(data);
491
492
// From string with encoding
493
ByteSequence fromString = ByteSequence.create("Hello World", StandardCharsets.UTF_8);
494
495
// From InputStream
496
try (InputStream input = new FileInputStream("data.bin")) {
497
ByteSequence fromStream = ByteSequence.create(input);
498
}
499
500
// Usage with Source
501
Source binarySource = Source.newBuilder("js", fromArray, "binary-data.js").build();
502
503
Context context = Context.create("js");
504
context.getBindings("js").putMember("binaryData", fromArray);
505
506
context.eval("js", """
507
console.log('Length:', binaryData.length);
508
console.log('First byte:', binaryData.byteAt(0));
509
510
// Convert to string (if text data)
511
let text = '';
512
for (let i = 0; i < binaryData.length; i++) {
513
text += String.fromCharCode(binaryData.byteAt(i));
514
}
515
console.log('Text:', text); // "Hello"
516
""");
517
```
518
519
### Custom ByteSequence Implementation
520
521
```java
522
public class MemoryMappedByteSequence implements ByteSequence {
523
private final MappedByteBuffer buffer;
524
private final int offset;
525
private final int length;
526
527
public MemoryMappedByteSequence(Path file) throws IOException {
528
try (RandomAccessFile randomAccessFile = new RandomAccessFile(file.toFile(), "r");
529
FileChannel channel = randomAccessFile.getChannel()) {
530
531
this.buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
532
this.offset = 0;
533
this.length = (int) channel.size();
534
}
535
}
536
537
private MemoryMappedByteSequence(MappedByteBuffer buffer, int offset, int length) {
538
this.buffer = buffer;
539
this.offset = offset;
540
this.length = length;
541
}
542
543
@Override
544
public int length() {
545
return length;
546
}
547
548
@Override
549
public byte byteAt(int index) {
550
if (index < 0 || index >= length) {
551
throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
552
}
553
return buffer.get(offset + index);
554
}
555
556
@Override
557
public ByteSequence subSequence(int startIndex, int endIndex) {
558
if (startIndex < 0 || endIndex > length || startIndex > endIndex) {
559
throw new IndexOutOfBoundsException();
560
}
561
return new MemoryMappedByteSequence(buffer, offset + startIndex, endIndex - startIndex);
562
}
563
564
@Override
565
public byte[] toByteArray() {
566
byte[] result = new byte[length];
567
for (int i = 0; i < length; i++) {
568
result[i] = byteAt(i);
569
}
570
return result;
571
}
572
}
573
```
574
575
## Message Transport and Communication
576
577
The polyglot API provides interfaces for message-based communication, useful for implementing client-server protocols or inter-process communication.
578
579
### MessageTransport Interface
580
581
```java { .api }
582
public interface MessageTransport {
583
/**
584
* Opens a message endpoint for communication.
585
* @param uri the endpoint URI
586
* @param peerEndpoint the peer endpoint for bidirectional communication
587
* @return the opened message endpoint
588
* @throws IOException if opening fails
589
*/
590
MessageEndpoint open(URI uri, MessageEndpoint peerEndpoint) throws IOException;
591
}
592
```
593
594
### MessageEndpoint Interface
595
596
```java { .api }
597
public interface MessageEndpoint extends AutoCloseable {
598
/**
599
* Sends a text message.
600
* @param text the text message
601
* @throws IOException if sending fails
602
*/
603
void sendText(String text) throws IOException;
604
605
/**
606
* Sends binary data.
607
* @param data the binary data
608
* @throws IOException if sending fails
609
*/
610
void sendBinary(ByteBuffer data) throws IOException;
611
612
/**
613
* Sends a ping message.
614
* @param data optional ping data
615
* @throws IOException if sending fails
616
*/
617
void sendPing(ByteBuffer data) throws IOException;
618
619
/**
620
* Sends a pong response.
621
* @param data optional pong data
622
* @throws IOException if sending fails
623
*/
624
void sendPong(ByteBuffer data) throws IOException;
625
626
/**
627
* Sends close message and closes the endpoint.
628
* @throws IOException if closing fails
629
*/
630
void sendClose() throws IOException;
631
632
/**
633
* Closes the endpoint.
634
*/
635
@Override
636
void close() throws IOException;
637
}
638
```
639
640
**Message Transport Example:**
641
642
```java
643
public class WebSocketTransport implements MessageTransport {
644
645
@Override
646
public MessageEndpoint open(URI uri, MessageEndpoint peerEndpoint) throws IOException {
647
return new WebSocketEndpoint(uri, peerEndpoint);
648
}
649
650
private static class WebSocketEndpoint implements MessageEndpoint {
651
private final URI uri;
652
private final MessageEndpoint peer;
653
private volatile boolean closed = false;
654
655
public WebSocketEndpoint(URI uri, MessageEndpoint peer) {
656
this.uri = uri;
657
this.peer = peer;
658
}
659
660
@Override
661
public void sendText(String text) throws IOException {
662
if (closed) throw new IOException("Endpoint is closed");
663
664
// Log message
665
System.out.printf("Sending text to %s: %s%n", uri, text);
666
667
// Simulate sending to peer
668
if (peer != null) {
669
// In real implementation, this would go through network
670
// peer.receiveText(text);
671
}
672
}
673
674
@Override
675
public void sendBinary(ByteBuffer data) throws IOException {
676
if (closed) throw new IOException("Endpoint is closed");
677
678
System.out.printf("Sending %d bytes to %s%n", data.remaining(), uri);
679
680
if (peer != null) {
681
// peer.receiveBinary(data);
682
}
683
}
684
685
@Override
686
public void sendPing(ByteBuffer data) throws IOException {
687
if (closed) throw new IOException("Endpoint is closed");
688
689
System.out.printf("Sending ping to %s%n", uri);
690
691
// Automatic pong response
692
if (peer != null) {
693
peer.sendPong(data);
694
}
695
}
696
697
@Override
698
public void sendPong(ByteBuffer data) throws IOException {
699
if (closed) throw new IOException("Endpoint is closed");
700
701
System.out.printf("Sending pong to %s%n", uri);
702
}
703
704
@Override
705
public void sendClose() throws IOException {
706
close();
707
}
708
709
@Override
710
public void close() throws IOException {
711
if (!closed) {
712
closed = true;
713
System.out.printf("Closed connection to %s%n", uri);
714
}
715
}
716
}
717
}
718
719
// Usage in Context configuration
720
Context context = Context.newBuilder("js")
721
.allowIO(IOAccess.newBuilder()
722
.allowHostSocketAccess(true)
723
.build())
724
.option("js.webSocket", "true") // Example: enable WebSocket support
725
.build();
726
```
727
728
## Process Handler Interface
729
730
ProcessHandler provides an abstraction for creating and managing external processes.
731
732
### ProcessHandler Interface
733
734
```java { .api }
735
public interface ProcessHandler {
736
/**
737
* Starts an external process.
738
* @param command the process command configuration
739
* @return the started process
740
* @throws IOException if process creation fails
741
*/
742
Process start(ProcessCommand command) throws IOException;
743
}
744
```
745
746
### ProcessCommand Configuration
747
748
```java { .api }
749
public final class ProcessCommand {
750
// Process command builder methods
751
public static ProcessCommand.Builder create(String... command);
752
public static ProcessCommand.Builder create(List<String> command);
753
754
public static final class Builder {
755
public Builder arguments(String... args);
756
public Builder arguments(List<String> args);
757
public Builder environment(Map<String, String> env);
758
public Builder directory(Path workingDirectory);
759
public Builder redirectInput(ProcessHandler.Redirect redirect);
760
public Builder redirectOutput(ProcessHandler.Redirect redirect);
761
public Builder redirectError(ProcessHandler.Redirect redirect);
762
public ProcessCommand build();
763
}
764
}
765
```
766
767
**Process Handler Example:**
768
769
```java
770
public class RestrictedProcessHandler implements ProcessHandler {
771
private final Set<String> allowedCommands;
772
private final Path allowedDirectory;
773
774
public RestrictedProcessHandler(Set<String> allowedCommands, Path allowedDirectory) {
775
this.allowedCommands = allowedCommands;
776
this.allowedDirectory = allowedDirectory;
777
}
778
779
@Override
780
public Process start(ProcessCommand command) throws IOException {
781
List<String> commandList = command.getCommand();
782
if (commandList.isEmpty()) {
783
throw new IOException("Empty command");
784
}
785
786
String executable = commandList.get(0);
787
if (!allowedCommands.contains(executable)) {
788
throw new SecurityException("Command not allowed: " + executable);
789
}
790
791
Path workingDir = command.getDirectory();
792
if (workingDir != null && !workingDir.startsWith(allowedDirectory)) {
793
throw new SecurityException("Working directory not allowed: " + workingDir);
794
}
795
796
// Log process creation
797
System.out.printf("Starting process: %s in %s%n", commandList, workingDir);
798
799
ProcessBuilder builder = new ProcessBuilder(commandList);
800
if (workingDir != null) {
801
builder.directory(workingDir.toFile());
802
}
803
804
// Restrict environment
805
builder.environment().clear();
806
builder.environment().put("PATH", "/usr/bin:/bin");
807
808
return builder.start();
809
}
810
}
811
812
// Usage
813
Set<String> allowedCommands = Set.of("echo", "cat", "ls", "grep");
814
Path sandboxDir = Paths.get("/safe/sandbox");
815
ProcessHandler processHandler = new RestrictedProcessHandler(allowedCommands, sandboxDir);
816
817
Context context = Context.newBuilder("js")
818
.allowIO(IOAccess.newBuilder()
819
.allowHostFileAccess(true)
820
.build())
821
.allowCreateProcess(true)
822
.option("js.processHandler", processHandler) // Example configuration
823
.build();
824
```
825
826
## I/O Security Best Practices
827
828
### 1. Path Traversal Prevention
829
830
```java
831
public class SecureFileSystem implements FileSystem {
832
private final Path rootPath;
833
834
private Path securePath(Path path) {
835
Path normalized = rootPath.resolve(path).normalize();
836
if (!normalized.startsWith(rootPath)) {
837
throw new SecurityException("Path traversal attempt: " + path);
838
}
839
return normalized;
840
}
841
842
@Override
843
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
844
Path securePath = securePath(path);
845
846
// Audit file access
847
AuditLogger.logFileAccess(securePath, options);
848
849
return Files.newByteChannel(securePath, options, attrs);
850
}
851
}
852
```
853
854
### 2. Resource Quotas
855
856
```java
857
public class QuotaFileSystem implements FileSystem {
858
private final AtomicLong bytesRead = new AtomicLong(0);
859
private final AtomicLong bytesWritten = new AtomicLong(0);
860
private final long maxBytesRead;
861
private final long maxBytesWritten;
862
863
public QuotaFileSystem(long maxBytesRead, long maxBytesWritten) {
864
this.maxBytesRead = maxBytesRead;
865
this.maxBytesWritten = maxBytesWritten;
866
}
867
868
@Override
869
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
870
SeekableByteChannel channel = Files.newByteChannel(path, options, attrs);
871
872
return new QuotaByteChannel(channel, bytesRead, bytesWritten, maxBytesRead, maxBytesWritten);
873
}
874
}
875
876
class QuotaByteChannel implements SeekableByteChannel {
877
private final SeekableByteChannel delegate;
878
private final AtomicLong totalBytesRead;
879
private final AtomicLong totalBytesWritten;
880
private final long maxRead;
881
private final long maxWrite;
882
883
// Implementation that tracks and limits I/O operations...
884
}
885
```
886
887
### 3. I/O Monitoring and Logging
888
889
```java
890
public class MonitoringFileSystem implements FileSystem {
891
private final FileSystem delegate;
892
private final IOMonitor monitor;
893
894
public MonitoringFileSystem(FileSystem delegate, IOMonitor monitor) {
895
this.delegate = delegate;
896
this.monitor = monitor;
897
}
898
899
@Override
900
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
901
monitor.onFileAccess(path, options);
902
903
long startTime = System.nanoTime();
904
try {
905
SeekableByteChannel channel = delegate.newByteChannel(path, options, attrs);
906
monitor.onFileOpenSuccess(path, System.nanoTime() - startTime);
907
return channel;
908
} catch (IOException e) {
909
monitor.onFileOpenFailure(path, e, System.nanoTime() - startTime);
910
throw e;
911
}
912
}
913
}
914
915
interface IOMonitor {
916
void onFileAccess(Path path, Set<? extends OpenOption> options);
917
void onFileOpenSuccess(Path path, long durationNanos);
918
void onFileOpenFailure(Path path, IOException error, long durationNanos);
919
}
920
```