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 <[email protected]> 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; + } +}
