You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rz...@apache.org on 2026/10/13 07:52:42 UTC
(tomee) 14/18: Keep RAFPassivater files in a private temp directory (#3082)
This is an automated email from the ASF dual-hosted git repository.
rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git
commit b2dbb1236ae8e858d6f402bff737ec180d629d3e
Author: Richard Zowalla <rz...@apache.org>
AuthorDate: Tue Oct 13 08:49:29 2026 +0200
Keep RAFPassivater files in a private temp directory (#3082)
Refuse pre-existing files there, and fix the file id and size recorded
per entry so activate reads back what passivate wrote.
(cherry picked from commit 3205f69fa69e41bf4112e9c85ad29f08a56ae03a)
---
.../apache/openejb/core/managed/RAFPassivater.java | 50 +++---
.../openejb/core/stateful/RAFPassivater.java | 49 +++---
.../openejb/core/managed/RAFPassivaterTest.java | 174 +++++++++++++++++++++
.../openejb/core/stateful/RAFPassivaterTest.java | 174 +++++++++++++++++++++
4 files changed, 404 insertions(+), 43 deletions(-)
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/core/managed/RAFPassivater.java b/container/openejb-core/src/main/java/org/apache/openejb/core/managed/RAFPassivater.java
index 4d1b026de7..0f40a3b724 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/core/managed/RAFPassivater.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/core/managed/RAFPassivater.java
@@ -22,7 +22,11 @@ import org.apache.openejb.spi.Serializer;
import org.apache.openejb.util.JavaSecurityManagers;
import java.io.File;
+import java.io.IOException;
import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
@@ -34,6 +38,7 @@ public class RAFPassivater implements PassivationStrategy {
int fileID;
Hashtable masterTable = new Hashtable();
+ private Path directory;
static class Pointer {
int fileid;
@@ -50,28 +55,32 @@ public class RAFPassivater implements PassivationStrategy {
public void init(final Properties props) throws SystemException {
}
+ // the files are deserialized on activation, so keep them in an owner-only
+ // directory with an unpredictable name instead of the shared tmp dir
+ private File passivationFile(final int id) throws IOException {
+ if (directory == null) {
+ final String tmp = JavaSecurityManagers.getSystemProperty("java.io.tmpdir", File.separator + "tmp");
+ directory = Files.createTempDirectory(Paths.get(tmp), "openejb-passivation-");
+ }
+ return directory.resolve("passivation" + id + ".ser").toFile();
+ }
+
+ private File createPassivationFile(final int id) throws IOException {
+ final File file = passivationFile(id);
+ // fails if the file already exists and never follows a link
+ Files.createFile(file.toPath());
+ return file;
+ }
+
public synchronized void passivate(final Map stateTable)
throws SystemException {
- try (final RandomAccessFile ras =
- new RandomAccessFile(JavaSecurityManagers.getSystemProperty("java.io.tmpdir", File.separator + "tmp") +
- File.separator + "passivation" + fileID + ".ser", "rw")) {
- fileID++;
-
+ final int id = fileID++;
+ try (final RandomAccessFile ras = new RandomAccessFile(createPassivationFile(id), "rw")) {
final Iterator iterator = stateTable.keySet().iterator();
- Pointer lastPointer = null;
while (iterator.hasNext()) {
- final Object id = iterator.next();
- final Object obj = stateTable.get(id);
- final byte[] bytes = Serializer.serialize(obj);
- final long filepointer = ras.getFilePointer();
-
- if (lastPointer == null) {
- lastPointer = new Pointer(fileID, filepointer, (int) filepointer);
- } else {
- lastPointer = new Pointer(fileID, filepointer, (int) (filepointer - lastPointer.filepointer));
- }
-
- masterTable.put(id, lastPointer);
+ final Object key = iterator.next();
+ final byte[] bytes = Serializer.serialize(stateTable.get(key));
+ masterTable.put(key, new Pointer(id, ras.getFilePointer(), bytes.length));
ras.write(bytes);
}
} catch (final Exception e) {
@@ -87,13 +96,10 @@ public class RAFPassivater implements PassivationStrategy {
return null;
}
- try (final RandomAccessFile ras =
- new RandomAccessFile(JavaSecurityManagers.getSystemProperty("java.io.tmpdir", File.separator + "tmp") +
- File.separator + "passivation" + pointer.fileid + ".ser", "r")) {
+ try (final RandomAccessFile ras = new RandomAccessFile(passivationFile(pointer.fileid), "r")) {
final byte[] bytes = new byte[pointer.bytesize];
ras.seek(pointer.filepointer);
ras.readFully(bytes);
- ras.close();
return Serializer.deserialize(bytes);
} catch (final Exception e) {
throw new SystemException(e);
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java b/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java
index 5058663a41..6a1cbbde2c 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java
@@ -22,7 +22,11 @@ import org.apache.openejb.spi.Serializer;
import org.apache.openejb.util.JavaSecurityManagers;
import java.io.File;
+import java.io.IOException;
import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -32,6 +36,7 @@ public class RAFPassivater implements PassivationStrategy {
int fileID;
HashMap masterTable = new HashMap();
+ private Path directory;
static class Pointer {
@@ -50,29 +55,33 @@ public class RAFPassivater implements PassivationStrategy {
public void init(final Properties props) throws SystemException {
}
+ // the files are deserialized on activation, so keep them in an owner-only
+ // directory with an unpredictable name instead of the shared tmp dir
+ private File passivationFile(final int id) throws IOException {
+ if (directory == null) {
+ final String tmp = JavaSecurityManagers.getSystemProperty("java.io.tmpdir", File.separator + "tmp");
+ directory = Files.createTempDirectory(Paths.get(tmp), "openejb-passivation-");
+ }
+ return directory.resolve("passivation" + id + ".ser").toFile();
+ }
+
+ private File createPassivationFile(final int id) throws IOException {
+ final File file = passivationFile(id);
+ // fails if the file already exists and never follows a link
+ Files.createFile(file.toPath());
+ return file;
+ }
+
@Override
public synchronized void passivate(final Map stateTable)
throws SystemException {
- try(final RandomAccessFile ras = new RandomAccessFile(
- JavaSecurityManagers.getSystemProperty("java.io.tmpdir", File.separator + "tmp") +
- File.separator + "passivation" + fileID + ".ser", "rw")) {
- fileID++;
-
+ final int id = fileID++;
+ try (final RandomAccessFile ras = new RandomAccessFile(createPassivationFile(id), "rw")) {
final Iterator iterator = stateTable.keySet().iterator();
- Pointer lastPointer = null;
while (iterator.hasNext()) {
- final Object id = iterator.next();
- final Object obj = stateTable.get(id);
- final byte[] bytes = Serializer.serialize(obj);
- final long filepointer = ras.getFilePointer();
-
- if (lastPointer == null) {
- lastPointer = new Pointer(fileID, filepointer, (int) filepointer);
- } else {
- lastPointer = new Pointer(fileID, filepointer, (int) (filepointer - lastPointer.filepointer));
- }
-
- masterTable.put(id, lastPointer);
+ final Object key = iterator.next();
+ final byte[] bytes = Serializer.serialize(stateTable.get(key));
+ masterTable.put(key, new Pointer(id, ras.getFilePointer(), bytes.length));
ras.write(bytes);
}
} catch (final Exception e) {
@@ -89,9 +98,7 @@ public class RAFPassivater implements PassivationStrategy {
return null;
}
- try (final RandomAccessFile ras =
- new RandomAccessFile(JavaSecurityManagers.getSystemProperty("java.io.tmpdir", File.separator + "tmp") +
- File.separator + "passivation" + pointer.fileid + ".ser", "r")) {
+ try (final RandomAccessFile ras = new RandomAccessFile(passivationFile(pointer.fileid), "r")) {
final byte[] bytes = new byte[pointer.bytesize];
ras.seek(pointer.filepointer);
ras.readFully(bytes);
diff --git a/container/openejb-core/src/test/java/org/apache/openejb/core/managed/RAFPassivaterTest.java b/container/openejb-core/src/test/java/org/apache/openejb/core/managed/RAFPassivaterTest.java
new file mode 100644
index 0000000000..663a0a4277
--- /dev/null
+++ b/container/openejb-core/src/test/java/org/apache/openejb/core/managed/RAFPassivaterTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.core.managed;
+
+import org.apache.openejb.SystemException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+public class RAFPassivaterTest {
+
+ private static final byte[] PLANTED = {1, 2, 3};
+
+ @Rule
+ public final TemporaryFolder folder = new TemporaryFolder();
+
+ private String originalTmpDir;
+ private Path tmp;
+
+ @Before
+ public void useOwnTmpDir() throws IOException {
+ originalTmpDir = System.getProperty("java.io.tmpdir");
+ tmp = folder.newFolder("tmp").toPath();
+ System.setProperty("java.io.tmpdir", tmp.toString());
+ }
+
+ @After
+ public void restoreTmpDir() {
+ System.setProperty("java.io.tmpdir", originalTmpDir);
+ }
+
+ @Test
+ public void activateReturnsPassivatedState() throws Exception {
+ final RAFPassivater passivater = new RAFPassivater();
+
+ final Map<Object, Object> first = new HashMap<>();
+ first.put("a", "x");
+ first.put("b", new ArrayList<>(List.of("some", "longer", "state")));
+ first.put("c", 42);
+ passivater.passivate(first);
+
+ final Map<Object, Object> second = new HashMap<>();
+ second.put("d", "y".repeat(1000));
+ second.put("e", new HashMap<>(Map.of("k", "v")));
+ passivater.passivate(second);
+
+ final Map<Object, Object> all = new HashMap<>(first);
+ all.putAll(second);
+ for (final Map.Entry<Object, Object> entry : all.entrySet()) {
+ assertEquals(entry.getValue(), passivater.activate(entry.getKey()));
+ }
+ assertNull(passivater.activate("unknown"));
+ }
+
+ @Test
+ public void passivatesIntoPrivateDirectory() throws Exception {
+ final RAFPassivater passivater = new RAFPassivater();
+ passivater.passivate(state("a"));
+ passivater.passivate(state("b"));
+
+ final Path directory = privateDirectory();
+ assertTrue(Files.isRegularFile(directory.resolve("passivation0.ser")));
+ assertTrue(Files.isRegularFile(directory.resolve("passivation1.ser")));
+ if (directory.getFileSystem().supportedFileAttributeViews().contains("posix")) {
+ assertEquals(EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE),
+ Files.getPosixFilePermissions(directory));
+ }
+ }
+
+ @Test
+ public void ignoresFilesPlantedInTmpDir() throws Exception {
+ final Path planted = Files.write(tmp.resolve("passivation0.ser"), PLANTED);
+ final Path target = Files.write(tmp.resolve("target"), PLANTED);
+ final boolean linked = link(tmp.resolve("passivation1.ser"), target);
+
+ final RAFPassivater passivater = new RAFPassivater();
+ passivater.passivate(state("a"));
+ passivater.passivate(state("b"));
+
+ assertEquals("a", passivater.activate("a"));
+ assertEquals("b", passivater.activate("b"));
+ assertArrayEquals(PLANTED, Files.readAllBytes(planted));
+ if (linked) {
+ assertArrayEquals(PLANTED, Files.readAllBytes(target));
+ }
+ }
+
+ @Test
+ public void refusesExistingFile() throws Exception {
+ final RAFPassivater passivater = new RAFPassivater();
+ passivater.passivate(state("a"));
+
+ final Path planted = Files.write(privateDirectory().resolve("passivation1.ser"), PLANTED);
+ assertRefused(passivater);
+ assertArrayEquals(PLANTED, Files.readAllBytes(planted));
+ }
+
+ @Test
+ public void refusesLink() throws Exception {
+ final RAFPassivater passivater = new RAFPassivater();
+ passivater.passivate(state("a"));
+
+ final Path target = Files.write(tmp.resolve("target"), PLANTED);
+ assumeTrue("symbolic links not supported", link(privateDirectory().resolve("passivation1.ser"), target));
+ assertRefused(passivater);
+ assertArrayEquals(PLANTED, Files.readAllBytes(target));
+ }
+
+ private Path privateDirectory() throws IOException {
+ try (final Stream<Path> entries = Files.list(tmp)) {
+ final List<Path> directories = entries.filter(Files::isDirectory).toList();
+ assertEquals("expected a single passivation directory in " + tmp, 1, directories.size());
+ return directories.get(0);
+ }
+ }
+
+ private static void assertRefused(final RAFPassivater passivater) {
+ try {
+ passivater.passivate(state("b"));
+ fail("an existing passivation file must not be reused");
+ } catch (final SystemException expected) {
+ // ok
+ }
+ }
+
+ private static boolean link(final Path link, final Path target) {
+ try {
+ Files.createSymbolicLink(link, target);
+ return true;
+ } catch (final UnsupportedOperationException | IOException e) {
+ return false;
+ }
+ }
+
+ private static Map<Object, Object> state(final String value) {
+ final Map<Object, Object> state = new HashMap<>();
+ state.put(value, value);
+ return state;
+ }
+}
diff --git a/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/RAFPassivaterTest.java b/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/RAFPassivaterTest.java
new file mode 100644
index 0000000000..896adfd8b2
--- /dev/null
+++ b/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/RAFPassivaterTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.core.stateful;
+
+import org.apache.openejb.SystemException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+public class RAFPassivaterTest {
+
+ private static final byte[] PLANTED = {1, 2, 3};
+
+ @Rule
+ public final TemporaryFolder folder = new TemporaryFolder();
+
+ private String originalTmpDir;
+ private Path tmp;
+
+ @Before
+ public void useOwnTmpDir() throws IOException {
+ originalTmpDir = System.getProperty("java.io.tmpdir");
+ tmp = folder.newFolder("tmp").toPath();
+ System.setProperty("java.io.tmpdir", tmp.toString());
+ }
+
+ @After
+ public void restoreTmpDir() {
+ System.setProperty("java.io.tmpdir", originalTmpDir);
+ }
+
+ @Test
+ public void activateReturnsPassivatedState() throws Exception {
+ final RAFPassivater passivater = new RAFPassivater();
+
+ final Map<Object, Object> first = new HashMap<>();
+ first.put("a", "x");
+ first.put("b", new ArrayList<>(List.of("some", "longer", "state")));
+ first.put("c", 42);
+ passivater.passivate(first);
+
+ final Map<Object, Object> second = new HashMap<>();
+ second.put("d", "y".repeat(1000));
+ second.put("e", new HashMap<>(Map.of("k", "v")));
+ passivater.passivate(second);
+
+ final Map<Object, Object> all = new HashMap<>(first);
+ all.putAll(second);
+ for (final Map.Entry<Object, Object> entry : all.entrySet()) {
+ assertEquals(entry.getValue(), passivater.activate(entry.getKey()));
+ }
+ assertNull(passivater.activate("unknown"));
+ }
+
+ @Test
+ public void passivatesIntoPrivateDirectory() throws Exception {
+ final RAFPassivater passivater = new RAFPassivater();
+ passivater.passivate(state("a"));
+ passivater.passivate(state("b"));
+
+ final Path directory = privateDirectory();
+ assertTrue(Files.isRegularFile(directory.resolve("passivation0.ser")));
+ assertTrue(Files.isRegularFile(directory.resolve("passivation1.ser")));
+ if (directory.getFileSystem().supportedFileAttributeViews().contains("posix")) {
+ assertEquals(EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE),
+ Files.getPosixFilePermissions(directory));
+ }
+ }
+
+ @Test
+ public void ignoresFilesPlantedInTmpDir() throws Exception {
+ final Path planted = Files.write(tmp.resolve("passivation0.ser"), PLANTED);
+ final Path target = Files.write(tmp.resolve("target"), PLANTED);
+ final boolean linked = link(tmp.resolve("passivation1.ser"), target);
+
+ final RAFPassivater passivater = new RAFPassivater();
+ passivater.passivate(state("a"));
+ passivater.passivate(state("b"));
+
+ assertEquals("a", passivater.activate("a"));
+ assertEquals("b", passivater.activate("b"));
+ assertArrayEquals(PLANTED, Files.readAllBytes(planted));
+ if (linked) {
+ assertArrayEquals(PLANTED, Files.readAllBytes(target));
+ }
+ }
+
+ @Test
+ public void refusesExistingFile() throws Exception {
+ final RAFPassivater passivater = new RAFPassivater();
+ passivater.passivate(state("a"));
+
+ final Path planted = Files.write(privateDirectory().resolve("passivation1.ser"), PLANTED);
+ assertRefused(passivater);
+ assertArrayEquals(PLANTED, Files.readAllBytes(planted));
+ }
+
+ @Test
+ public void refusesLink() throws Exception {
+ final RAFPassivater passivater = new RAFPassivater();
+ passivater.passivate(state("a"));
+
+ final Path target = Files.write(tmp.resolve("target"), PLANTED);
+ assumeTrue("symbolic links not supported", link(privateDirectory().resolve("passivation1.ser"), target));
+ assertRefused(passivater);
+ assertArrayEquals(PLANTED, Files.readAllBytes(target));
+ }
+
+ private Path privateDirectory() throws IOException {
+ try (final Stream<Path> entries = Files.list(tmp)) {
+ final List<Path> directories = entries.filter(Files::isDirectory).toList();
+ assertEquals("expected a single passivation directory in " + tmp, 1, directories.size());
+ return directories.get(0);
+ }
+ }
+
+ private static void assertRefused(final RAFPassivater passivater) {
+ try {
+ passivater.passivate(state("b"));
+ fail("an existing passivation file must not be reused");
+ } catch (final SystemException expected) {
+ // ok
+ }
+ }
+
+ private static boolean link(final Path link, final Path target) {
+ try {
+ Files.createSymbolicLink(link, target);
+ return true;
+ } catch (final UnsupportedOperationException | IOException e) {
+ return false;
+ }
+ }
+
+ private static Map<Object, Object> state(final String value) {
+ final Map<Object, Object> state = new HashMap<>();
+ state.put(value, value);
+ return state;
+ }
+}