25
25
import java .nio .file .Files ;
26
26
import java .nio .file .Path ;
27
27
import java .nio .file .StandardCopyOption ;
28
+ import java .util .Arrays ;
28
29
29
30
import org .exist .storage .journal .LogException ;
30
31
import org .exist .storage .txn .Txn ;
32
+ import org .exist .util .FileUtils ;
33
+ import org .exist .util .crypto .digest .MessageDigest ;
34
+ import org .exist .util .crypto .digest .StreamableDigest ;
31
35
32
36
/**
33
37
* @author Adam Retter <[email protected] >
34
38
*
35
39
* Serialized binary format is as follows:
36
40
*
37
- * [walDataPathLen, walDataPath, createPathLen, createPath]
41
+ * [walDataPathLen, walDataPath, walDataDigestType, walDataDigest, createPathLen, createPath]
38
42
*
39
- * walDataPathLen: 2 bytes, unsigned short
40
- * walDataPath: var length bytes, UTF-8 encoded java.lang.String
41
- * createPathLen: 2 bytes, unsigned short
42
- * createPath: var length bytes, UTF-8 encoded java.lang.String
43
+ * walDataPathLen: 2 bytes, unsigned short
44
+ * walDataPath: var length bytes, UTF-8 encoded java.lang.String
45
+ * walDataDigestType: 1 byte
46
+ * walDataDigest: n-bytes, where n is deteremined by {@code walDataDigestType}
47
+ * createPathLen: 2 bytes, unsigned short
48
+ * createPath: var length bytes, UTF-8 encoded java.lang.String
43
49
*/
44
50
public class CreateBinaryLoggable extends AbstractBinaryLoggable {
45
- private byte [] walDataPath ; // the data to use for the file to be created
46
- private byte [] createPath ; // the file to be created
51
+ private byte [] walDataPath ; // the data to use for the file to be created
52
+ private MessageDigest walDataDigest ;
53
+ private byte [] createPath ; // the file to be created
47
54
48
55
/**
49
56
* Creates a new instance of CreateBinaryLoggable.
50
57
*
51
58
* @param broker The database broker.
52
59
* @param txn The database transaction.
53
60
* @param walData A copy of the data that was stored for {@code create} file before it was actually created (i.e. the new value).
61
+ * @param walDataDigest digest of the {@code walData} content
54
62
* @param create The file that is to be created in the database.
55
63
*/
56
- public CreateBinaryLoggable (final DBBroker broker , final Txn txn , final Path walData , final Path create ) {
64
+ public CreateBinaryLoggable (final DBBroker broker , final Txn txn , final Path walData ,
65
+ final MessageDigest walDataDigest , final Path create ) {
57
66
super (NativeBroker .LOG_CREATE_BINARY , txn .getId ());
58
67
this .walDataPath = getPathData (walData );
59
68
checkPathLen (getClass ().getSimpleName (), "walDataPath" , walDataPath );
69
+ this .walDataDigest = walDataDigest ;
70
+
60
71
this .createPath = getPathData (create );
61
72
checkPathLen (getClass ().getSimpleName (), "createPath" , createPath );
62
73
}
@@ -76,6 +87,8 @@ public int getLogSize() {
76
87
return
77
88
2 +
78
89
walDataPath .length +
90
+ 1 +
91
+ walDataDigest .getDigestType ().getDigestLengthBytes () +
79
92
2 +
80
93
createPath .length ;
81
94
}
@@ -84,6 +97,8 @@ public int getLogSize() {
84
97
public void write (final ByteBuffer out ) {
85
98
out .putShort (asUnsignedShort (walDataPath .length ));
86
99
out .put (walDataPath );
100
+ writeMessageDigest (out , walDataDigest );
101
+
87
102
out .putShort (asUnsignedShort (createPath .length ));
88
103
out .put (createPath );
89
104
}
@@ -93,6 +108,7 @@ public void read(final ByteBuffer in) {
93
108
final int walDataPathLen = asSignedInt (in .getShort ());
94
109
this .walDataPath = new byte [walDataPathLen ];
95
110
in .get (walDataPath );
111
+ this .walDataDigest = readMessageDigest (in );
96
112
97
113
final int createPathLen = asSignedInt (in .getShort ());
98
114
this .createPath = new byte [createPathLen ];
@@ -113,7 +129,28 @@ public void redo() throws LogException {
113
129
}
114
130
115
131
try {
132
+ // ensure the integrity of the walData file
133
+ final StreamableDigest walDataStreamableDigest = walDataDigest .getDigestType ().newStreamableDigest ();
134
+ FileUtils .digest (walData , walDataStreamableDigest );
135
+ if (!Arrays .equals (walDataStreamableDigest .getMessageDigest (), walDataDigest .getValue ())) {
136
+ throw new LogException ("Cannot redo creation of binary resource: "
137
+ + create .toAbsolutePath ().toString () + " from "
138
+ + walData .toAbsolutePath ().toString () + ", digest of walData file is invalid" );
139
+ }
140
+
141
+ // perform the redo - copy
116
142
Files .copy (walData , create , StandardCopyOption .REPLACE_EXISTING );
143
+
144
+ // ensure the integrity of the copy
145
+ walDataStreamableDigest .reset ();
146
+ FileUtils .digest (create , walDataStreamableDigest );
147
+ if (!Arrays .equals (walDataStreamableDigest .getMessageDigest (), walDataDigest .getValue ())) {
148
+ FileUtils .deleteQuietly (create );
149
+ throw new LogException ("Cannot redo creation of binary resource: "
150
+ + create .toAbsolutePath ().toString () + " from "
151
+ + walData .toAbsolutePath ().toString () + ", checksum of new create file is invalid" );
152
+ }
153
+
117
154
} catch (final IOException ioe ) {
118
155
throw new LogException ("Cannot redo creation of binary resource: "
119
156
+ create .toAbsolutePath ().toString (), ioe );
@@ -127,20 +164,36 @@ public void undo() throws LogException {
127
164
final Path walData = getPath (walDataPath );
128
165
final Path create = getPath (createPath );
129
166
130
- // ensure integrity of write-ahead-data file first!
131
- if (!Files .exists (walData )) {
132
- throw new LogException ("Cannot redo creation of binary resource: "
133
- + create .toAbsolutePath ().toString () + ", missing write ahead data: "
134
- + walData .toAbsolutePath ().toString ());
135
- }
136
-
137
-
138
- // cover the use-case where the previous operation was a delete
139
- if (!Files .exists (create )) {
140
- return ;
141
- }
142
-
143
167
try {
168
+ // ensure integrity of the walData file first!
169
+ if (!Files .exists (walData )) {
170
+ throw new LogException ("Cannot redo creation of binary resource: "
171
+ + create .toAbsolutePath ().toString () + ", missing write ahead data: "
172
+ + walData .toAbsolutePath ().toString ());
173
+ }
174
+
175
+ // ensure integrity of walData file by checksum
176
+ final StreamableDigest walDataStreamableDigest = walDataDigest .getDigestType ().newStreamableDigest ();
177
+ FileUtils .digest (walData , walDataStreamableDigest );
178
+ if (!Arrays .equals (walDataStreamableDigest .getMessageDigest (), walDataDigest .getValue ())) {
179
+ throw new LogException ("Cannot undo creation of binary resource: "
180
+ + create .toAbsolutePath ().toString () + ", checksum of walData file is invalid" );
181
+ }
182
+
183
+ // cover the use-case where the previous operation was a delete
184
+ if (!Files .exists (create )) {
185
+ return ;
186
+ }
187
+
188
+ // ensure that no one has interfered with the createdFile
189
+ walDataStreamableDigest .reset ();
190
+ FileUtils .digest (create , walDataStreamableDigest );
191
+ if (!Arrays .equals (walDataStreamableDigest .getMessageDigest (), walDataDigest .getValue ())) {
192
+ throw new LogException ("Cannot undo creation of binary resource: "
193
+ + create .toAbsolutePath ().toString () + ", checksum is invalid" );
194
+ }
195
+
196
+ // preform the undo - delete
144
197
Files .delete (create );
145
198
} catch (final IOException ioe ) {
146
199
throw new LogException ("Cannot undo creation of binary resource: "
0 commit comments