Skip to content

Commit 29dac0f

Browse files
BlockReader and BlockWriter impls will now attempt to repair directory and file permissions
Signed-off-by: Matt Peterson <[email protected]>
1 parent b1ea596 commit 29dac0f

File tree

7 files changed

+282
-48
lines changed

7 files changed

+282
-48
lines changed

server/docker/Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ EXPOSE 8080
77
# Define version
88
ARG VERSION
99

10+
# Create a non-root user and group
1011
ARG UNAME=hedera
1112
ARG UID=2000
1213
ARG GID=2000

server/src/main/java/com/hedera/block/server/persistence/storage/BlockAsDirReader.java

+67-6
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,24 @@
2626
import java.io.FileInputStream;
2727
import java.io.FileNotFoundException;
2828
import java.io.IOException;
29+
import java.nio.file.Files;
2930
import java.nio.file.Path;
31+
import java.nio.file.attribute.FileAttribute;
32+
import java.nio.file.attribute.PosixFilePermission;
3033
import java.util.Optional;
34+
import java.util.Set;
3135

3236
public class BlockAsDirReader implements BlockReader<Block> {
3337

3438
private final System.Logger LOGGER = System.getLogger(getClass().getName());
3539

3640
final Path blockNodeRootPath;
41+
private final FileAttribute<Set<PosixFilePermission>> filePerms;
3742

38-
public BlockAsDirReader(final String key, final Config config) {
43+
public BlockAsDirReader(
44+
final String key,
45+
final Config config,
46+
final FileAttribute<Set<PosixFilePermission>> filePerms) {
3947

4048
LOGGER.log(System.Logger.Level.INFO, "Initializing FileSystemBlockReader");
4149

@@ -45,15 +53,21 @@ public BlockAsDirReader(final String key, final Config config) {
4553
LOGGER.log(System.Logger.Level.INFO, "Block Node Root Path: " + blockNodeRootPath);
4654

4755
this.blockNodeRootPath = blockNodeRootPath;
56+
this.filePerms = filePerms;
57+
}
58+
59+
public BlockAsDirReader(final String key, final Config config) {
60+
this(key, config, Util.defaultPerms);
4861
}
4962

63+
@Override
5064
public Optional<Block> read(final long blockNumber) throws IOException {
5165

5266
// Construct the path to the requested Block
5367
final Path blockPath = blockNodeRootPath.resolve(String.valueOf(blockNumber));
5468

5569
// Verify attributes of the Block
56-
if (!isVerified(blockPath)) {
70+
if (!isVerified(blockNodeRootPath, blockPath)) {
5771
return Optional.empty();
5872
}
5973

@@ -100,17 +114,64 @@ private Optional<BlockItem> readBlockItem(final String blockItemPath) throws IOE
100114
}
101115
}
102116

103-
private boolean isVerified(final Path blockPath) {
104-
if (!blockPath.toFile().isDirectory()) {
105-
LOGGER.log(System.Logger.Level.ERROR, "Block directory not found: " + blockPath);
106-
return false;
117+
private boolean isVerified(final Path blockNodeRootPath, final Path blockPath) {
118+
119+
if (!blockNodeRootPath.toFile().canRead()) {
120+
LOGGER.log(
121+
System.Logger.Level.ERROR,
122+
"Block node root path directory not readable: {0}",
123+
blockNodeRootPath);
124+
LOGGER.log(
125+
System.Logger.Level.ERROR,
126+
"Attempting to repair the permissions: " + blockNodeRootPath);
127+
if (!setPerms(blockNodeRootPath)) {
128+
return false;
129+
}
130+
131+
// retest
132+
if (!blockNodeRootPath.toFile().canRead()) {
133+
LOGGER.log(
134+
System.Logger.Level.ERROR,
135+
"Block node root path directory still not readable: {0}",
136+
blockNodeRootPath);
137+
return false;
138+
}
107139
}
108140

109141
if (!blockPath.toFile().canRead()) {
110142
LOGGER.log(System.Logger.Level.ERROR, "Block directory not readable: " + blockPath);
143+
LOGGER.log(
144+
System.Logger.Level.ERROR,
145+
"Attempting to repair block directory permissions: " + blockPath);
146+
if (!setPerms(blockPath)) {
147+
return false;
148+
}
149+
150+
// retest
151+
if (!blockPath.toFile().canRead()) {
152+
LOGGER.log(
153+
System.Logger.Level.ERROR,
154+
"Block directory still not readable: " + blockPath);
155+
return false;
156+
}
157+
}
158+
159+
if (!blockPath.toFile().isDirectory()) {
160+
LOGGER.log(System.Logger.Level.ERROR, "Block directory not found: " + blockPath);
111161
return false;
112162
}
113163

114164
return true;
115165
}
166+
167+
private boolean setPerms(final Path path) {
168+
try {
169+
Files.setPosixFilePermissions(path, filePerms.value());
170+
return true;
171+
} catch (IOException e) {
172+
LOGGER.log(System.Logger.Level.ERROR, "Error setting permissions on: " + path, e);
173+
}
174+
175+
return false;
176+
}
116177
}

server/src/main/java/com/hedera/block/server/persistence/storage/BlockAsDirWriter.java

+60-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
import java.io.IOException;
2626
import java.nio.file.Files;
2727
import java.nio.file.Path;
28+
import java.nio.file.attribute.FileAttribute;
29+
import java.nio.file.attribute.PosixFilePermission;
30+
import java.nio.file.attribute.PosixFilePermissions;
31+
import java.util.Set;
2832

2933
public class BlockAsDirWriter implements BlockWriter<BlockItem> {
3034

@@ -33,8 +37,13 @@ public class BlockAsDirWriter implements BlockWriter<BlockItem> {
3337
private final Path blockNodeRootPath;
3438
private long blockNodeFileNameIndex = 0;
3539
private Path currentBlockDir;
40+
private final FileAttribute<Set<PosixFilePermission>> filePerms;
3641

37-
public BlockAsDirWriter(final String key, final Config config) throws IOException {
42+
public BlockAsDirWriter(
43+
final String key,
44+
final Config config,
45+
final FileAttribute<Set<PosixFilePermission>> filePerms)
46+
throws IOException {
3847

3948
LOGGER.log(System.Logger.Level.INFO, "Initializing FileSystemBlockStorage");
4049

@@ -52,8 +61,14 @@ public BlockAsDirWriter(final String key, final Config config) throws IOExceptio
5261
createPath(blockNodeRootPath, System.Logger.Level.INFO);
5362

5463
this.blockNodeRootPath = blockNodeRootPath;
64+
this.filePerms = filePerms;
65+
}
66+
67+
public BlockAsDirWriter(final String key, final Config config) throws IOException {
68+
this(key, config, Util.defaultPerms);
5569
}
5670

71+
@Override
5772
public void write(final BlockItem blockItem) throws IOException {
5873

5974
if (blockItem.hasHeader()) {
@@ -74,11 +89,54 @@ public void write(final BlockItem blockItem) throws IOException {
7489
}
7590
}
7691

92+
private void removeBlock() throws IOException {
93+
// Remove the block directory
94+
final Path blockPath = blockNodeRootPath.resolve(currentBlockDir);
95+
96+
final Set<PosixFilePermission> writePerm =
97+
PosixFilePermissions.asFileAttribute(Set.of(PosixFilePermission.OWNER_WRITE))
98+
.value();
99+
Files.setPosixFilePermissions(blockPath, writePerm);
100+
101+
Files.delete(blockPath);
102+
103+
LOGGER.log(System.Logger.Level.INFO, "Deleted block node root directory: " + blockPath);
104+
}
105+
77106
private void resetState(final BlockItem blockItem) throws IOException {
78107
// Here a "block" is represented as a directory of BlockItems.
79108
// Create the "block" directory based on the block_number
80109
currentBlockDir = Path.of(String.valueOf(blockItem.getHeader().getBlockNumber()));
81110

111+
final boolean isReadable = Files.isReadable(blockNodeRootPath);
112+
final boolean isWritable = Files.isWritable(blockNodeRootPath);
113+
if (!isWritable || !isReadable) {
114+
if (!isWritable) {
115+
LOGGER.log(
116+
System.Logger.Level.ERROR,
117+
"Block node root directory is not writable. Attempting to change the"
118+
+ " permissions.");
119+
}
120+
121+
if (!isReadable) {
122+
LOGGER.log(
123+
System.Logger.Level.ERROR,
124+
"Block node root directory is not readable. Attempting to change the"
125+
+ " permissions.");
126+
}
127+
128+
try {
129+
// Attempt to restore the permissions on the block node root directory
130+
Files.setPosixFilePermissions(blockNodeRootPath, filePerms.value());
131+
} catch (IOException e) {
132+
LOGGER.log(
133+
System.Logger.Level.ERROR,
134+
"Error setting permissions on the block node root directory",
135+
e);
136+
throw e;
137+
}
138+
}
139+
82140
// Construct the path to the block directory
83141
createPath(blockNodeRootPath.resolve(currentBlockDir), System.Logger.Level.DEBUG);
84142

@@ -96,7 +154,7 @@ private String calculateBlockItemPath() {
96154
private void createPath(Path blockNodePath, System.Logger.Level logLevel) throws IOException {
97155
// Initialize the Block directory if it does not exist
98156
if (Files.notExists(blockNodePath)) {
99-
Files.createDirectory(blockNodePath);
157+
Files.createDirectory(blockNodePath, filePerms);
100158
LOGGER.log(logLevel, "Created block node root directory: " + blockNodePath);
101159
} else {
102160
LOGGER.log(logLevel, "Using existing block node root directory: " + blockNodePath);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (C) 2024 Hedera Hashgraph, LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.hedera.block.server.persistence.storage;
18+
19+
import java.nio.file.attribute.FileAttribute;
20+
import java.nio.file.attribute.PosixFilePermission;
21+
import java.nio.file.attribute.PosixFilePermissions;
22+
import java.util.Set;
23+
24+
public final class Util {
25+
private Util() {}
26+
27+
// Default permissions: rwxr-xr-x
28+
public static final FileAttribute<Set<PosixFilePermission>> defaultPerms =
29+
PosixFilePermissions.asFileAttribute(
30+
Set.of(
31+
PosixFilePermission.OWNER_READ,
32+
PosixFilePermission.OWNER_WRITE,
33+
PosixFilePermission.OWNER_EXECUTE,
34+
PosixFilePermission.GROUP_READ,
35+
PosixFilePermission.GROUP_EXECUTE,
36+
PosixFilePermission.OTHERS_READ,
37+
PosixFilePermission.OTHERS_EXECUTE));
38+
}

server/src/test/java/com/hedera/block/server/BlockStreamServiceIT.java

+15-18
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
package com.hedera.block.server;
1818

1919
import static com.hedera.block.protos.BlockStreamService.*;
20-
import static com.hedera.block.protos.BlockStreamService.PublishStreamResponse.EndOfStream;
21-
import static com.hedera.block.protos.BlockStreamService.PublishStreamResponse.ItemAcknowledgement;
22-
import static com.hedera.block.protos.BlockStreamService.PublishStreamResponse.PublishStreamResponseCode;
20+
import static com.hedera.block.protos.BlockStreamService.PublishStreamResponse.*;
2321
import static com.hedera.block.server.util.PersistTestUtils.generateBlockItems;
2422
import static org.junit.jupiter.api.Assertions.assertFalse;
2523
import static org.mockito.Mockito.*;
@@ -29,10 +27,7 @@
2927
import com.hedera.block.server.mediator.LiveStreamMediatorImpl;
3028
import com.hedera.block.server.mediator.StreamMediator;
3129
import com.hedera.block.server.persistence.FileSystemPersistenceHandler;
32-
import com.hedera.block.server.persistence.storage.BlockAsDirReader;
33-
import com.hedera.block.server.persistence.storage.BlockAsDirWriter;
34-
import com.hedera.block.server.persistence.storage.BlockReader;
35-
import com.hedera.block.server.persistence.storage.BlockWriter;
30+
import com.hedera.block.server.persistence.storage.*;
3631
import com.hedera.block.server.producer.ItemAckBuilder;
3732
import com.hedera.block.server.util.TestUtils;
3833
import com.lmax.disruptor.BatchEventProcessor;
@@ -44,8 +39,8 @@
4439
import java.io.IOException;
4540
import java.nio.file.Files;
4641
import java.nio.file.Path;
42+
import java.nio.file.attribute.FileAttribute;
4743
import java.nio.file.attribute.PosixFilePermission;
48-
import java.nio.file.attribute.PosixFilePermissions;
4944
import java.security.NoSuchAlgorithmException;
5045
import java.util.*;
5146
import java.util.concurrent.ConcurrentHashMap;
@@ -303,7 +298,7 @@ public void testSubAndUnsubWhileStreaming() throws IOException, InterruptedExcep
303298
BlockItemEventHandler<ObjectEvent<SubscribeStreamResponse>>,
304299
BatchEventProcessor<ObjectEvent<SubscribeStreamResponse>>>
305300
subscribers = new LinkedHashMap<>();
306-
final var streamMediator = buildStreamMediator(subscribers);
301+
final var streamMediator = buildStreamMediator(subscribers, Util.defaultPerms);
307302
final var blockStreamService = buildBlockStreamService(streamMediator);
308303

309304
// Pass a StreamObserver to the producer as Helidon does
@@ -389,7 +384,10 @@ public void testMediatorExceptionHandlingWhenPersistenceFailure()
389384
BlockItemEventHandler<ObjectEvent<SubscribeStreamResponse>>,
390385
BatchEventProcessor<ObjectEvent<SubscribeStreamResponse>>>
391386
subscribers = new ConcurrentHashMap<>();
392-
final var streamMediator = buildStreamMediator(subscribers);
387+
388+
// Initialize the underlying BlockReader and BlockWriter with ineffective
389+
// permissions to repair the file system.
390+
final var streamMediator = buildStreamMediator(subscribers, TestUtils.getNoPerms());
393391
final var blockStreamService = buildBlockStreamService(streamMediator);
394392

395393
// Register the web server to confirm
@@ -452,12 +450,9 @@ public void testMediatorExceptionHandlingWhenPersistenceFailure()
452450
verify(webServer, times(1)).stop();
453451
}
454452

455-
private static final String NO_WRITE = "r-xr-xr-x";
456-
457453
private void removeRootPathWritePerms(final Config config) throws IOException {
458454
final Path blockNodeRootPath = Path.of(config.get(JUNIT).asString().get());
459-
final Set<PosixFilePermission> perms = PosixFilePermissions.fromString(NO_WRITE);
460-
Files.setPosixFilePermissions(blockNodeRootPath, perms);
455+
Files.setPosixFilePermissions(blockNodeRootPath, TestUtils.getNoWrite().value());
461456
}
462457

463458
private static void verifySubscribeStreamResponse(
@@ -499,19 +494,21 @@ private static SubscribeStreamResponse buildSubscribeStreamResponse(BlockItem bl
499494

500495
private StreamMediator<BlockItem, ObjectEvent<SubscribeStreamResponse>> buildStreamMediator()
501496
throws IOException {
502-
return buildStreamMediator(new ConcurrentHashMap<>());
497+
return buildStreamMediator(new ConcurrentHashMap<>(), Util.defaultPerms);
503498
}
504499

505500
private StreamMediator<BlockItem, ObjectEvent<SubscribeStreamResponse>> buildStreamMediator(
506501
final Map<
507502
BlockItemEventHandler<ObjectEvent<SubscribeStreamResponse>>,
508503
BatchEventProcessor<ObjectEvent<SubscribeStreamResponse>>>
509-
subscribers)
504+
subscribers,
505+
FileAttribute<Set<PosixFilePermission>> filePerms)
510506
throws IOException {
511507

512508
// Initialize with concrete a concrete BlockReader, BlockWriter and Mediator
513-
final BlockReader<Block> blockReader = new BlockAsDirReader(JUNIT, testConfig);
514-
final BlockWriter<BlockItem> blockWriter = new BlockAsDirWriter(JUNIT, testConfig);
509+
final BlockReader<Block> blockReader = new BlockAsDirReader(JUNIT, testConfig, filePerms);
510+
final BlockWriter<BlockItem> blockWriter =
511+
new BlockAsDirWriter(JUNIT, testConfig, filePerms);
515512
return new LiveStreamMediatorImpl(
516513
subscribers, new FileSystemPersistenceHandler(blockReader, blockWriter));
517514
}

0 commit comments

Comments
 (0)