or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context-management.mdengine-management.mdexecution-monitoring.mdindex.mdio-abstractions.mdproxy-system.mdsecurity-configuration.mdsource-management.mdvalue-operations.md

io-abstractions.mddocs/

0

# I/O Abstractions

1

2

I/O abstractions provide pluggable interfaces for controlled file system access, process execution, and message communication. These abstractions enable sandboxed environments with custom I/O behavior while maintaining security boundaries and enabling advanced use cases like virtual file systems and custom process handlers.

3

4

## Capabilities

5

6

### File System Abstraction

7

8

Custom file system implementations for controlled file access.

9

10

```java { .api }

11

public interface FileSystem {

12

Path parsePath(URI uri);

13

Path parsePath(String path);

14

void checkAccess(Path path, Set<AccessMode> modes, LinkOption... linkOptions);

15

void createDirectory(Path dir, FileAttribute<?>... attrs);

16

void delete(Path path);

17

SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs);

18

DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter);

19

Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options);

20

void setAttribute(Path path, String attribute, Object value, LinkOption... options);

21

void copy(Path source, Path target, CopyOption... options);

22

void move(Path source, Path target, CopyOption... options);

23

Path toAbsolutePath(Path path);

24

Path toRealPath(Path path, LinkOption... linkOptions);

25

String getSeparator();

26

String getPathSeparator();

27

}

28

```

29

30

**Usage:**

31

32

```java

33

public class RestrictedFileSystem implements FileSystem {

34

private final Path allowedRoot;

35

private final FileSystem delegate;

36

37

public RestrictedFileSystem(Path allowedRoot) {

38

this.allowedRoot = allowedRoot.toAbsolutePath();

39

this.delegate = FileSystems.getDefault();

40

}

41

42

@Override

43

public Path parsePath(String path) {

44

Path parsed = delegate.getPath(path);

45

if (!isAllowed(parsed)) {

46

throw new SecurityException("Access denied: " + path);

47

}

48

return parsed;

49

}

50

51

@Override

52

public void checkAccess(Path path, Set<AccessMode> modes, LinkOption... linkOptions) {

53

if (!isAllowed(path)) {

54

throw new AccessDeniedException(path.toString());

55

}

56

delegate.checkAccess(path, modes, linkOptions);

57

}

58

59

@Override

60

public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) {

61

if (!isAllowed(path)) {

62

throw new AccessDeniedException(path.toString());

63

}

64

return delegate.newByteChannel(path, options, attrs);

65

}

66

67

private boolean isAllowed(Path path) {

68

try {

69

Path absolute = path.toAbsolutePath();

70

return absolute.startsWith(allowedRoot);

71

} catch (Exception e) {

72

return false;

73

}

74

}

75

76

// ... implement other methods with similar access control

77

}

78

79

// Usage with context

80

FileSystem restrictedFS = new RestrictedFileSystem(Paths.get("/safe/directory"));

81

82

IOAccess ioAccess = IOAccess.newBuilder()

83

.fileSystem(restrictedFS)

84

.allowHostFileAccess(false)

85

.build();

86

87

Context context = Context.newBuilder("js")

88

.allowIO(ioAccess)

89

.build();

90

91

// JavaScript file operations are now restricted to /safe/directory

92

context.eval("js", "const fs = require('fs'); fs.readFileSync('/safe/directory/file.txt')"); // Works

93

// context.eval("js", "fs.readFileSync('/etc/passwd')"); // Throws SecurityException

94

```

95

96

### Virtual File System

97

98

Create in-memory file systems for testing or sandboxing:

99

100

```java

101

public class MemoryFileSystem implements FileSystem {

102

private final Map<String, byte[]> files = new ConcurrentHashMap<>();

103

private final Map<String, Set<String>> directories = new ConcurrentHashMap<>();

104

105

public MemoryFileSystem() {

106

directories.put("/", new ConcurrentSkipListSet<>());

107

}

108

109

@Override

110

public Path parsePath(String path) {

111

return Paths.get(path);

112

}

113

114

@Override

115

public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) {

116

String pathStr = path.toString();

117

byte[] content = files.getOrDefault(pathStr, new byte[0]);

118

119

if (options.contains(StandardOpenOption.WRITE) || options.contains(StandardOpenOption.CREATE)) {

120

return new WritableMemoryChannel(pathStr, content, this::writeFile);

121

} else {

122

return new ReadOnlyMemoryChannel(content);

123

}

124

}

125

126

@Override

127

public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) {

128

String dirStr = dir.toString();

129

Set<String> children = directories.getOrDefault(dirStr, Collections.emptySet());

130

131

List<Path> paths = children.stream()

132

.map(child -> Paths.get(dirStr, child))

133

.filter(path -> {

134

try {

135

return filter == null || filter.accept(path);

136

} catch (IOException e) {

137

return false;

138

}

139

})

140

.collect(Collectors.toList());

141

142

return new MemoryDirectoryStream(paths);

143

}

144

145

private void writeFile(String path, byte[] content) {

146

files.put(path, content);

147

148

// Update parent directory

149

Path parent = Paths.get(path).getParent();

150

if (parent != null) {

151

String parentStr = parent.toString();

152

directories.computeIfAbsent(parentStr, k -> new ConcurrentSkipListSet<>())

153

.add(Paths.get(path).getFileName().toString());

154

}

155

}

156

157

// ... implement other methods

158

}

159

```

160

161

### Process Handler

162

163

Custom process execution for controlled subprocess management.

164

165

```java { .api }

166

public interface ProcessHandler {

167

Process start(ProcessCommand command) throws IOException;

168

}

169

170

public final class ProcessCommand {

171

public static ProcessCommand create(List<String> command);

172

public List<String> getCommand();

173

public String getDirectory();

174

public Map<String, String> getEnvironment();

175

public boolean isRedirectErrorStream();

176

}

177

```

178

179

**Usage:**

180

181

```java

182

public class RestrictedProcessHandler implements ProcessHandler {

183

private final Set<String> allowedCommands;

184

private final Path allowedWorkingDir;

185

186

public RestrictedProcessHandler(Set<String> allowedCommands, Path allowedWorkingDir) {

187

this.allowedCommands = allowedCommands;

188

this.allowedWorkingDir = allowedWorkingDir;

189

}

190

191

@Override

192

public Process start(ProcessCommand command) throws IOException {

193

List<String> cmd = command.getCommand();

194

if (cmd.isEmpty()) {

195

throw new IOException("Empty command");

196

}

197

198

String executable = cmd.get(0);

199

if (!allowedCommands.contains(executable)) {

200

throw new SecurityException("Command not allowed: " + executable);

201

}

202

203

String workingDir = command.getDirectory();

204

if (workingDir != null && !Paths.get(workingDir).startsWith(allowedWorkingDir)) {

205

throw new SecurityException("Working directory not allowed: " + workingDir);

206

}

207

208

ProcessBuilder pb = new ProcessBuilder(cmd);

209

if (workingDir != null) {

210

pb.directory(new File(workingDir));

211

}

212

213

// Filter environment variables

214

Map<String, String> env = command.getEnvironment();

215

if (env != null) {

216

pb.environment().clear();

217

env.entrySet().stream()

218

.filter(entry -> isSafeEnvironmentVariable(entry.getKey()))

219

.forEach(entry -> pb.environment().put(entry.getKey(), entry.getValue()));

220

}

221

222

return pb.start();

223

}

224

225

private boolean isSafeEnvironmentVariable(String name) {

226

// Only allow safe environment variables

227

return !name.startsWith("SECRET_") && !name.contains("PASSWORD");

228

}

229

}

230

231

// Usage

232

Set<String> allowedCommands = Set.of("echo", "cat", "grep", "sed");

233

Path allowedDir = Paths.get("/tmp/sandbox");

234

235

ProcessHandler processHandler = new RestrictedProcessHandler(allowedCommands, allowedDir);

236

237

IOAccess ioAccess = IOAccess.newBuilder()

238

.processHandler(processHandler)

239

.allowHostSocketAccess(false)

240

.build();

241

242

Context context = Context.newBuilder("js")

243

.allowIO(ioAccess)

244

.build();

245

246

// JavaScript can only execute allowed commands in allowed directories

247

```

248

249

### Message Transport

250

251

Custom message transport for inter-language communication.

252

253

```java { .api }

254

public interface MessageTransport {

255

MessageEndpoint open(URI uri, MessageEndpoint endpoint) throws IOException, MessageTransport.VetoException;

256

257

public static final class VetoException extends Exception {

258

public VetoException(String message);

259

}

260

}

261

262

public interface MessageEndpoint {

263

void sendText(String text) throws IOException;

264

void sendBinary(ByteBuffer data) throws IOException;

265

void sendPing(ByteBuffer data) throws IOException;

266

void sendPong(ByteBuffer data) throws IOException;

267

void sendClose() throws IOException;

268

}

269

```

270

271

**Usage:**

272

273

```java

274

public class CustomMessageTransport implements MessageTransport {

275

private final Map<String, MessageHandler> handlers = new HashMap<>();

276

277

public CustomMessageTransport() {

278

registerHandler("echo", new EchoHandler());

279

registerHandler("log", new LogHandler());

280

}

281

282

public void registerHandler(String protocol, MessageHandler handler) {

283

handlers.put(protocol, handler);

284

}

285

286

@Override

287

public MessageEndpoint open(URI uri, MessageEndpoint endpoint) throws IOException, VetoException {

288

String scheme = uri.getScheme();

289

MessageHandler handler = handlers.get(scheme);

290

291

if (handler == null) {

292

throw new VetoException("Unsupported protocol: " + scheme);

293

}

294

295

return handler.createEndpoint(uri, endpoint);

296

}

297

}

298

299

public interface MessageHandler {

300

MessageEndpoint createEndpoint(URI uri, MessageEndpoint clientEndpoint) throws IOException;

301

}

302

303

public class EchoHandler implements MessageHandler {

304

@Override

305

public MessageEndpoint createEndpoint(URI uri, MessageEndpoint clientEndpoint) {

306

return new EchoEndpoint(clientEndpoint);

307

}

308

}

309

310

public class EchoEndpoint implements MessageEndpoint {

311

private final MessageEndpoint client;

312

313

public EchoEndpoint(MessageEndpoint client) {

314

this.client = client;

315

}

316

317

@Override

318

public void sendText(String text) throws IOException {

319

// Echo back the text

320

client.sendText("Echo: " + text);

321

}

322

323

@Override

324

public void sendBinary(ByteBuffer data) throws IOException {

325

// Echo back the binary data

326

client.sendBinary(data);

327

}

328

329

// ... implement other methods

330

}

331

332

// Usage

333

MessageTransport transport = new CustomMessageTransport();

334

335

IOAccess ioAccess = IOAccess.newBuilder()

336

.messageTransport(transport)

337

.build();

338

339

Context context = Context.newBuilder("js")

340

.allowIO(ioAccess)

341

.build();

342

343

// JavaScript can now use custom message protocols

344

```

345

346

### Byte Sequence Abstraction

347

348

Work with binary data in a language-neutral way.

349

350

```java { .api }

351

public interface ByteSequence {

352

int length();

353

byte byteAt(int index);

354

ByteSequence subSequence(int start, int end);

355

byte[] toByteArray();

356

}

357

```

358

359

**Usage:**

360

361

```java

362

public class FileByteSequence implements ByteSequence {

363

private final RandomAccessFile file;

364

private final long offset;

365

private final int length;

366

367

public FileByteSequence(RandomAccessFile file, long offset, int length) {

368

this.file = file;

369

this.offset = offset;

370

this.length = length;

371

}

372

373

@Override

374

public int length() {

375

return length;

376

}

377

378

@Override

379

public byte byteAt(int index) {

380

if (index < 0 || index >= length) {

381

throw new IndexOutOfBoundsException();

382

}

383

try {

384

file.seek(offset + index);

385

return file.readByte();

386

} catch (IOException e) {

387

throw new RuntimeException(e);

388

}

389

}

390

391

@Override

392

public ByteSequence subSequence(int start, int end) {

393

if (start < 0 || end > length || start > end) {

394

throw new IndexOutOfBoundsException();

395

}

396

return new FileByteSequence(file, offset + start, end - start);

397

}

398

399

@Override

400

public byte[] toByteArray() {

401

byte[] result = new byte[length];

402

try {

403

file.seek(offset);

404

file.readFully(result);

405

return result;

406

} catch (IOException e) {

407

throw new RuntimeException(e);

408

}

409

}

410

}

411

```

412

413

## Advanced I/O Patterns

414

415

### Layered File Systems

416

417

Combine multiple file systems for complex access patterns:

418

419

```java

420

public class LayeredFileSystem implements FileSystem {

421

private final List<FileSystem> layers;

422

423

public LayeredFileSystem(FileSystem... layers) {

424

this.layers = Arrays.asList(layers);

425

}

426

427

@Override

428

public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) {

429

// Try each layer in order

430

for (FileSystem fs : layers) {

431

try {

432

return fs.newByteChannel(path, options, attrs);

433

} catch (NoSuchFileException e) {

434

continue; // Try next layer

435

}

436

}

437

throw new NoSuchFileException(path.toString());

438

}

439

440

// ... implement other methods with similar layering logic

441

}

442

443

// Usage: Create a file system that checks memory first, then disk

444

FileSystem layered = new LayeredFileSystem(

445

new MemoryFileSystem(),

446

new RestrictedFileSystem(Paths.get("/allowed"))

447

);

448

```

449

450

### Audit File System

451

452

Wrap file systems to log all access:

453

454

```java

455

public class AuditFileSystem implements FileSystem {

456

private final FileSystem delegate;

457

private final Logger logger;

458

459

public AuditFileSystem(FileSystem delegate, Logger logger) {

460

this.delegate = delegate;

461

this.logger = logger;

462

}

463

464

@Override

465

public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) {

466

logger.info("File access: {} with options: {}", path, options);

467

return delegate.newByteChannel(path, options, attrs);

468

}

469

470

@Override

471

public void delete(Path path) {

472

logger.warn("File deletion: {}", path);

473

delegate.delete(path);

474

}

475

476

// ... wrap other methods with logging

477

}

478

```

479

480

### Process Pool Handler

481

482

Manage process execution with resource pooling:

483

484

```java

485

public class PooledProcessHandler implements ProcessHandler {

486

private final ExecutorService executor;

487

private final Semaphore processLimit;

488

489

public PooledProcessHandler(int maxConcurrentProcesses) {

490

this.executor = Executors.newCachedThreadPool();

491

this.processLimit = new Semaphore(maxConcurrentProcesses);

492

}

493

494

@Override

495

public Process start(ProcessCommand command) throws IOException {

496

try {

497

processLimit.acquire();

498

} catch (InterruptedException e) {

499

throw new IOException("Process limit acquisition interrupted", e);

500

}

501

502

ProcessBuilder pb = new ProcessBuilder(command.getCommand());

503

Process process = pb.start();

504

505

// Release permit when process completes

506

executor.submit(() -> {

507

try {

508

process.waitFor();

509

} catch (InterruptedException e) {

510

Thread.currentThread().interrupt();

511

} finally {

512

processLimit.release();

513

}

514

});

515

516

return process;

517

}

518

}

519

```

520

521

## Performance Considerations

522

523

- **Caching**: Implement caching in file systems for frequently accessed files

524

- **Lazy Loading**: Use lazy loading for directory listings and file metadata

525

- **Resource Management**: Properly close channels, streams, and processes

526

- **Thread Safety**: Ensure I/O implementations are thread-safe if used across contexts

527

- **Memory Usage**: Be mindful of memory usage in virtual file systems and byte sequences

528

529

## Error Handling

530

531

I/O operations should handle errors appropriately:

532

533

```java

534

public class SafeFileSystem implements FileSystem {

535

private final FileSystem delegate;

536

537

@Override

538

public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) {

539

try {

540

return delegate.newByteChannel(path, options, attrs);

541

} catch (AccessDeniedException e) {

542

throw new PolyglotException("File access denied: " + path, e);

543

} catch (NoSuchFileException e) {

544

throw new PolyglotException("File not found: " + path, e);

545

} catch (IOException e) {

546

throw new PolyglotException("I/O error accessing file: " + path, e);

547

}

548

}

549

550

// ... similar error handling for other methods

551

}

552

```