1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.newsclub.net.unix.memory;
19
20 import java.io.Closeable;
21 import java.io.FileDescriptor;
22 import java.io.IOException;
23 import java.lang.foreign.Arena;
24 import java.lang.foreign.MemorySegment;
25 import java.nio.ByteBuffer;
26 import java.nio.channels.ClosedChannelException;
27 import java.nio.channels.FileChannel;
28 import java.nio.channels.FileChannel.MapMode;
29 import java.nio.file.attribute.PosixFilePermission;
30 import java.nio.file.attribute.PosixFilePermissions;
31 import java.util.Collections;
32 import java.util.Map;
33 import java.util.Objects;
34 import java.util.Set;
35 import java.util.WeakHashMap;
36
37 import org.newsclub.net.unix.AFSocket;
38 import org.newsclub.net.unix.FileChannelSupplier;
39 import org.newsclub.net.unix.FileDescriptorCast;
40 import org.newsclub.net.unix.MemoryImplUtilInternal;
41
42 import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
43
44
45
46
47
48
49 @SuppressFBWarnings("OVERLY_PERMISSIVE_FILE_PERMISSION")
50 public final class SharedMemory implements Closeable {
51 private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS = PosixFilePermissions
52 .fromString("rw-rw-rw-");
53
54
55
56
57 static Map<FileDescriptor, Long> FD_MEMORY;
58
59
60
61
62
63 public static final int MUTEX_SEGMENT_SIZE = 8;
64
65
66
67
68
69 static final int FUTEX32_SEGMENT_SIZE = 4;
70
71 private static final Map<String, Integer> MAP_MODES = Map.of(
72 "READ_ONLY", MemoryImplUtilInternal.MMODE_READ,
73 "READ_WRITE", (MemoryImplUtilInternal.MMODE_READ | MemoryImplUtilInternal.MMODE_WRITE),
74 "PRIVATE", (MemoryImplUtilInternal.MMODE_READ | MemoryImplUtilInternal.MMODE_WRITE
75 | MemoryImplUtilInternal.MMODE_COPY_ON_WRITE),
76
77 "READ_ONLY_SYNC", (MemoryImplUtilInternal.MMODE_READ | MemoryImplUtilInternal.MMODE_SYNC),
78 "READ_WRITE_SYNC", (MemoryImplUtilInternal.MMODE_READ | MemoryImplUtilInternal.MMODE_WRITE
79 | MemoryImplUtilInternal.MMODE_SYNC)
80 );
81
82 private final String name;
83
84 private final boolean knownReadOnly;
85 private final boolean unlinkUponClose;
86
87 static MemoryImplUtilInternal UTIL = null;
88
89 private final SharedMemoryCleaner cleaner;
90
91 private final long size;
92
93 private SharedMemory(FileDescriptor fd) throws IOException {
94 this(fd, -1, null, 0);
95 }
96
97 private SharedMemory(FileDescriptor fd, long size, String name, int mopts) throws IOException {
98 super();
99 if (size == -1) {
100 this.size = determineSize(fd);
101 } else {
102 this.size = size;
103 }
104 Objects.requireNonNull(fd);
105 this.cleaner = new SharedMemoryCleaner(null, this, fd);
106 this.name = name;
107 this.knownReadOnly = isReadOnly(mopts);
108 this.unlinkUponClose = (mopts & MemoryImplUtilInternal.MOPT_UNLINK_UPON_CLOSE) != 0;
109 }
110
111 private static long determineSize(FileDescriptor fd) throws IOException {
112 Map<FileDescriptor, Long> map = FD_MEMORY;
113 if (map != null) {
114 synchronized (map) {
115 Long knownSize = map.get(fd);
116 if (knownSize != null) {
117 return knownSize;
118 }
119 }
120 }
121 return getUtil().sizeOfSharedMemory(fd);
122
123 }
124
125 static boolean isUtilLoaded() {
126 return UTIL != null;
127 }
128
129 static MemoryImplUtilInternal getUtil() {
130 AFSocket.isSupported();
131
132 MemoryImplUtilInternal util = UTIL;
133 if (util == null) {
134 throw new IllegalStateException("MemoryImplUtilInternal not initialized");
135 }
136 return util;
137 }
138
139
140
141
142
143
144 public static synchronized void init(MemoryImplUtilInternal util) {
145 if (util != null) {
146 if (UTIL == null) {
147 UTIL = util;
148 if (util.needToTrackSharedMemory()) {
149 FD_MEMORY = new WeakHashMap<>();
150 } else {
151 FD_MEMORY = null;
152 }
153 } else {
154 throw new IllegalStateException();
155 }
156 }
157 }
158
159
160
161
162
163
164
165
166
167 public static SharedMemory using(FileDescriptor fd) throws IOException {
168 return new SharedMemory(fd);
169 }
170
171
172
173
174
175
176
177
178
179
180
181
182 public static SharedMemory createExclusively(String name, long minimumLength,
183 SharedMemoryOption... options) throws IOException {
184 return createExclusively(name, minimumLength, DEFAULT_PERMISSIONS, options);
185 }
186
187
188
189
190
191
192
193
194
195
196
197
198 public static SharedMemory createExclusively(String name, long minimumLength,
199 Set<PosixFilePermission> perms, SharedMemoryOption... options) throws IOException {
200 return shmOpen(name, perms, toOptions(options) | MemoryImplUtilInternal.MOPT_CREAT
201 | MemoryImplUtilInternal.MOPT_EXCL, minimumLength);
202 }
203
204
205
206
207
208
209
210
211
212
213
214
215 public static SharedMemory createOrOpenExisting(String name, long minimumLength,
216 SharedMemoryOption... options) throws IOException {
217 return createOrOpenExisting(name, minimumLength, DEFAULT_PERMISSIONS, options);
218 }
219
220
221
222
223
224
225
226
227
228
229
230
231 public static SharedMemory createOrOpenExisting(String name, long minimumLength,
232 Set<PosixFilePermission> perms, SharedMemoryOption... options) throws IOException {
233 return shmOpen(name, perms, toOptions(options) | MemoryImplUtilInternal.MOPT_CREAT,
234 minimumLength);
235 }
236
237
238
239
240
241
242
243
244
245
246
247
248 public static SharedMemory createOrReuseExisting(String name, long minimumLength,
249 SharedMemoryOption... options) throws IOException {
250 return createOrReuseExisting(name, minimumLength, DEFAULT_PERMISSIONS, options);
251 }
252
253
254
255
256
257
258
259
260
261
262
263
264
265 public static SharedMemory createOrReuseExisting(String name, long minimumLength,
266 Set<PosixFilePermission> perms, SharedMemoryOption... options) throws IOException {
267 return shmOpen(name, perms, toOptions(options) | MemoryImplUtilInternal.MOPT_CREAT
268 | MemoryImplUtilInternal.MOPT_TRUNC, minimumLength);
269 }
270
271
272
273
274
275
276
277
278
279
280 public static SharedMemory openExisting(String name, SharedMemoryOption... options)
281 throws IOException {
282 return shmOpen(name, DEFAULT_PERMISSIONS, toOptions(options), 0);
283 }
284
285
286
287
288
289
290
291
292 public static SharedMemory createAnonymous(long minimumLength) throws IOException {
293 return createAnonymous(minimumLength, (SharedMemoryOption[]) null);
294 }
295
296
297
298
299
300
301
302
303
304 public static SharedMemory createAnonymous(long minimumLength, SharedMemoryOption... options)
305 throws IOException {
306 int mopts = toOptions(options) | MemoryImplUtilInternal.MOPT_CREAT
307 | MemoryImplUtilInternal.MOPT_TRUNC;
308 return shmOpen0(null, Collections.emptySet(), mopts, minimumLength);
309 }
310
311 private static SharedMemory shmOpen(String name, Set<PosixFilePermission> perms, int mopts,
312 long minimumLength) throws IOException {
313 name = checkShmName(name);
314 return shmOpen0(name, perms, mopts, minimumLength);
315 }
316
317 @SuppressFBWarnings("USO_UNSAFE_ACCESSIBLE_OBJECT_SYNCHRONIZATION")
318 private static SharedMemory shmOpen0(String name, Set<PosixFilePermission> perms, int mopts,
319 long minimumLength) throws IOException {
320 MemoryImplUtilInternal util = getUtil();
321
322 FileDescriptor fd = new FileDescriptor();
323 long size = util.shmOpen(fd, name, minimumLength, toMode(perms), mopts);
324 SharedMemory sm = new SharedMemory(fd, size, name, mopts);
325
326 Map<FileDescriptor, Long> map = FD_MEMORY;
327 if (map != null) {
328 synchronized (map) {
329 map.put(fd, size);
330 }
331 }
332 return sm;
333 }
334
335 private static boolean isReadOnly(int mopts) {
336 return (mopts & MemoryImplUtilInternal.MOPT_RDONLY) != 0;
337 }
338
339
340
341
342
343
344
345
346
347
348 public static void unlinkShared(String name) throws IOException {
349 getUtil().shmUnlink(checkShmName(name));
350 }
351
352
353
354
355
356
357 public FileDescriptor getFileDescriptor() {
358 return cleaner.fd;
359 }
360
361
362
363
364
365
366
367
368
369
370
371 FileChannel asMappableFileChannel() throws IOException {
372 if (!cleaner.fd.valid()) {
373 throw new ClosedChannelException();
374 }
375 if (FD_MEMORY != null) {
376 throw new UnsupportedOperationException();
377 }
378 if (knownReadOnly) {
379 return FileDescriptorCast.using(cleaner.fd).as(FileChannelSupplier.ReadOnly.class).get();
380 } else {
381 return FileDescriptorCast.using(cleaner.fd).as(FileChannel.class);
382 }
383 }
384
385
386
387
388
389
390
391
392
393
394 public MemorySegment asMappedMemorySegment(MapMode mapMode) throws IOException {
395 return asMappedMemorySegment(mapMode, null, 0);
396 }
397
398
399
400
401
402
403
404
405
406
407
408
409
410 public MemorySegment asMappedMemorySegment(MapMode mapMode, Arena arena) throws IOException {
411 return asMappedMemorySegment(mapMode, arena, 0);
412 }
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431 public MemorySegment asMappedMemorySegment(MapMode mapMode, Arena arena, int duplicates)
432 throws IOException {
433 return asMappedMemorySegment(mapMode, arena, 0, -1, duplicates);
434 }
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455 public MemorySegment asMappedMemorySegment(MapMode mapMode, Arena arena, long offset, long length,
456 int duplicates) throws IOException {
457 if (offset < 0) {
458 throw new IllegalArgumentException("startOffset");
459 } else if (length < -1) {
460 throw new IllegalArgumentException("length");
461 }
462
463
464 MemorySegment arenaSegment;
465 if (arena == null) {
466 arenaSegment = cleaner.getArenaSegment();
467 } else {
468 arenaSegment = arena.allocate(0);
469 }
470
471 int mmode = resolveMmode(mapMode);
472 if (length == -1) {
473
474
475
476 length = size;
477 }
478
479 ByteBuffer buf = getUtil().mmapShm(arenaSegment, cleaner.fd, offset, length, mmode, duplicates);
480 return asRegisteredMemorySegment(cleaner, buf, (mmode
481 & MemoryImplUtilInternal.MMODE_WRITE) != 0, duplicates);
482 }
483
484 static MemorySegment asRegisteredMemorySegment(SharedMemoryCleaner cleaner, ByteBuffer buf,
485 boolean rw) {
486 return asRegisteredMemorySegment(cleaner, buf, rw, 0);
487 }
488
489 private static MemorySegment asRegisteredMemorySegment(SharedMemoryCleaner cleaner,
490 ByteBuffer buf, boolean rw, int duplicates) {
491 if (!rw) {
492
493
494
495 buf = buf.asReadOnlyBuffer();
496 }
497 MemorySegment ms = MemorySegment.ofBuffer(buf);
498 cleaner.registerMemorySegment(ms, duplicates);
499 return ms;
500 }
501
502
503
504
505
506
507
508 @SuppressWarnings("DoNotCallSuggester")
509 public void addSeals(Set<MemorySeal> seals) throws IOException {
510 throw new IOException("Unsupported");
511 }
512
513
514
515
516
517
518
519 public Set<MemorySeal> getSeals() throws IOException {
520 return Collections.emptySet();
521 }
522
523 private static String checkShmName(String name) {
524 Objects.requireNonNull(name);
525 if (name.length() == 0) {
526 throw new IllegalArgumentException("Name cannot be empty");
527 }
528 if (name.indexOf('/', 1) != -1) {
529 throw new IllegalArgumentException("Name must not contain extra slashes");
530 }
531 if (name.charAt(0) == '/') {
532 return name;
533 } else {
534 return "/" + name;
535 }
536 }
537
538 private static int toMode(Set<PosixFilePermission> perms) {
539 int mode = 0;
540 if (perms == null) {
541 perms = DEFAULT_PERMISSIONS;
542 }
543 for (PosixFilePermission perm : perms) {
544 switch (perm) {
545 case OWNER_READ:
546 mode |= MemoryImplUtilInternal.S_IRUSR;
547 break;
548 case OWNER_WRITE:
549 mode |= MemoryImplUtilInternal.S_IWUSR;
550 break;
551 case GROUP_READ:
552 mode |= MemoryImplUtilInternal.S_IRGRP;
553 break;
554 case GROUP_WRITE:
555 mode |= MemoryImplUtilInternal.S_IWGRP;
556 break;
557 case OTHERS_READ:
558 mode |= MemoryImplUtilInternal.S_IROTH;
559 break;
560 case OTHERS_WRITE:
561 mode |= MemoryImplUtilInternal.S_IWOTH;
562 break;
563 default:
564 throw new IllegalArgumentException("Unsupported permission: " + perm);
565 }
566 }
567 return mode;
568 }
569
570 private static int toOptions(SharedMemoryOption[] options) {
571 int opt = 0;
572 if (options != null) {
573 for (SharedMemoryOption option : options) {
574 opt |= option.getOpt();
575 }
576 }
577 return opt;
578 }
579
580
581
582
583
584
585 @Override
586 public void close() throws IOException {
587 boolean valid = cleaner.fd.valid();
588
589 cleaner.close();
590 if (unlinkUponClose && valid && name != null) {
591 MemoryImplUtilInternal util = getUtil();
592 util.shmUnlink(name);
593 }
594 }
595
596 String getName() {
597 return name;
598 }
599
600 private static int resolveMmode(MapMode mapMode) {
601 String modeString = mapMode.toString();
602 Integer mmode = MAP_MODES.get(modeString);
603 if (mmode == null) {
604 throw new UnsupportedOperationException("MapMode");
605 }
606 return mmode;
607 }
608
609
610
611
612
613
614
615
616 public static long defaultAllocationSize() {
617 return getUtil().getSharedMemoryAllocationSize();
618 }
619
620 Futex futex(MemorySegment addr) throws IOException {
621 return futex(addr, false, false);
622 }
623
624 Futex futex(MemorySegment addr, boolean wakeUpOnClose) throws IOException {
625 return futex(addr, wakeUpOnClose, false);
626 }
627
628 Futex futex(MemorySegment addr, boolean wakeUpOnClose, boolean zeroValueOnClose)
629 throws IOException {
630 if (addr.isReadOnly()) {
631 throw new IOException("MemorySegment is read-only");
632 }
633 cleaner.checkCovered(addr);
634 Futex futex = new Futex32(addr, zeroValueOnClose);
635 if (wakeUpOnClose) {
636 cleaner.registerFutex(futex);
637 }
638 return futex;
639 }
640
641
642
643
644
645
646
647
648
649 public SharedMutex mutex(MemorySegment addr) throws IOException {
650 return mutex(addr, false);
651 }
652
653 private SharedMutex mutex(MemorySegment addr, boolean unlockOnClose) throws IOException {
654 if (addr.isReadOnly()) {
655 throw new IOException("MemorySegment is read-only");
656 }
657 if (addr.byteSize() != 8) {
658 throw new IOException("MemorySegment must be exactly 8 bytes long");
659 }
660
661 cleaner.checkCovered(addr);
662
663 Futex32 futex = new Futex32(addr.asSlice(0, 4), unlockOnClose);
664 if (unlockOnClose) {
665 cleaner.registerFutex(futex);
666 }
667
668 return futex.mutex();
669 }
670
671
672
673
674
675
676 public long byteSize() {
677 return size;
678 }
679 }