Skip to content

Commit 00b929a

Browse files
authored
Merge pull request #277 from sourcefrog/write-mode
Distinguish overwrite vs create new in Transport
2 parents e1f198b + 4b6254b commit 00b929a

File tree

10 files changed

+112
-55
lines changed

10 files changed

+112
-55
lines changed

src/blockdir.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use rayon::prelude::*;
3232
use serde::{Deserialize, Serialize};
3333
use tracing::{debug, warn};
3434
use tracing::{instrument, trace};
35+
use transport::WriteMode;
3536

3637
use crate::compress::snappy::{Compressor, Decompressor};
3738
use crate::counters::Counter;
@@ -131,7 +132,19 @@ impl BlockDir {
131132
let hex_hash = hash.to_string();
132133
let relpath = block_relpath(&hash);
133134
self.transport.create_dir(subdir_relpath(&hex_hash))?;
134-
self.transport.write_file(&relpath, &compressed)?;
135+
match self
136+
.transport
137+
.write_file(&relpath, &compressed, WriteMode::CreateNew)
138+
{
139+
Ok(()) => {}
140+
Err(err) if err.kind() == transport::ErrorKind::AlreadyExists => {
141+
// let's assume the contents are correct
142+
}
143+
Err(err) => {
144+
warn!(?err, ?hash, "Error writing block");
145+
return Err(err.into());
146+
}
147+
}
135148
stats.written_blocks += 1;
136149
stats.uncompressed_bytes += uncomp_len;
137150
stats.compressed_bytes += comp_len;
@@ -386,11 +399,8 @@ mod test {
386399
let monitor = TestMonitor::arc();
387400
let subdir = tempdir.path().join(subdir_relpath("123"));
388401
create_dir(&subdir).unwrap();
389-
write(
390-
subdir.join(format!("{}{}", TMP_PREFIX, "123123123")),
391-
b"123",
392-
)
393-
.unwrap();
402+
// Write a temp file as was created by earlier versions of the code.
403+
write(subdir.join("tmp123123123"), b"123").unwrap();
394404
let blocks = blockdir
395405
.blocks(monitor.clone())
396406
.unwrap()

src/gc_lock.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
//! delete but before starting to actually delete them, we check that no
3232
//! new bands have been created.
3333
34+
use transport::WriteMode;
35+
3436
use crate::*;
3537

3638
pub static GC_LOCK: &str = "GC_LOCK";
@@ -63,7 +65,9 @@ impl GarbageCollectionLock {
6365
if archive.transport().is_file(GC_LOCK).unwrap_or(true) {
6466
return Err(Error::GarbageCollectionLockHeld);
6567
}
66-
archive.transport().write_file(GC_LOCK, b"{}\n")?;
68+
archive
69+
.transport()
70+
.write_file(GC_LOCK, b"{}\n", WriteMode::CreateNew)?;
6771
Ok(GarbageCollectionLock { archive, band_id })
6872
}
6973

src/index.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::transport::Transport;
2323
use itertools::Itertools;
2424
use time::OffsetDateTime;
2525
use tracing::{debug, debug_span, error};
26+
use transport::WriteMode;
2627

2728
use crate::compress::snappy::{Compressor, Decompressor};
2829
use crate::counters::Counter;
@@ -253,7 +254,8 @@ impl IndexWriter {
253254
self.transport.create_dir(&subdir_relpath(self.sequence))?;
254255
}
255256
let compressed_bytes = self.compressor.compress(&json)?;
256-
self.transport.write_file(&relpath, &compressed_bytes)?;
257+
self.transport
258+
.write_file(&relpath, &compressed_bytes, WriteMode::CreateNew)?;
257259
self.hunks_written += 1;
258260
monitor.count(Counter::IndexWrites, 1);
259261
monitor.count(Counter::IndexWriteCompressedBytes, compressed_bytes.len());

src/jsonio.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use std::path::PathBuf;
1818

1919
use serde::de::DeserializeOwned;
2020

21-
use crate::transport::{self, Transport};
21+
use crate::transport::{self, Transport, WriteMode};
2222

2323
#[derive(Debug, thiserror::Error)]
2424
pub enum Error {
@@ -54,7 +54,7 @@ where
5454
})?;
5555
s.push('\n');
5656
transport
57-
.write_file(relpath, s.as_bytes())
57+
.write_file(relpath, s.as_bytes(), WriteMode::CreateNew)
5858
.map_err(Error::from)
5959
}
6060

src/lib.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,6 @@ pub const ARCHIVE_VERSION: &str = "0.6";
9292

9393
pub const SYMLINKS_SUPPORTED: bool = cfg!(target_family = "unix");
9494

95-
/// Temporary files in the archive have this prefix.
96-
const TMP_PREFIX: &str = "tmp";
97-
9895
/// Metadata file in the band directory.
9996
static BAND_HEAD_FILENAME: &str = "BANDHEAD";
10097

src/transport.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ impl Transport {
116116
}
117117
}
118118

119-
pub fn write_file(&self, relpath: &str, content: &[u8]) -> Result<()> {
120-
self.protocol.write_file(relpath, content)
119+
pub fn write_file(&self, relpath: &str, content: &[u8], mode: WriteMode) -> Result<()> {
120+
self.protocol.write_file(relpath, content, mode)
121121
}
122122

123123
pub fn create_dir(&self, relpath: &str) -> Result<()> {
@@ -163,9 +163,18 @@ impl fmt::Debug for Transport {
163163
}
164164
}
165165

166+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167+
pub enum WriteMode {
168+
/// Create the file if it does not exist, or overwrite it if it does.
169+
Overwrite,
170+
171+
/// Create the file if it does not exist, or fail if it does.
172+
CreateNew,
173+
}
174+
166175
trait Protocol: Send + Sync {
167176
fn read_file(&self, path: &str) -> Result<Bytes>;
168-
fn write_file(&self, relpath: &str, content: &[u8]) -> Result<()>;
177+
fn write_file(&self, relpath: &str, content: &[u8], mode: WriteMode) -> Result<()>;
169178
fn list_dir(&self, relpath: &str) -> Result<ListDir>;
170179
fn create_dir(&self, relpath: &str) -> Result<()>;
171180
fn metadata(&self, relpath: &str) -> Result<Metadata>;

src/transport/local.rs

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ use std::sync::Arc;
1919
use std::{io, path};
2020

2121
use bytes::Bytes;
22-
use tracing::{instrument, trace, warn};
22+
use tracing::{error, instrument, trace, warn};
2323
use url::Url;
2424

25-
use super::{Error, ListDir, Metadata, Result};
25+
use super::{Error, ListDir, Metadata, Result, WriteMode};
2626

2727
pub(super) struct Protocol {
2828
path: PathBuf,
@@ -68,28 +68,31 @@ impl super::Protocol for Protocol {
6868
}
6969

7070
#[instrument(skip(self, content))]
71-
fn write_file(&self, relpath: &str, content: &[u8]) -> Result<()> {
71+
fn write_file(&self, relpath: &str, content: &[u8], write_mode: WriteMode) -> Result<()> {
7272
// TODO: Just write directly; remove if the write fails.
7373
let full_path = self.full_path(relpath);
74-
let dir = full_path.parent().unwrap();
75-
let context = |err| super::Error::io_error(&full_path, err);
76-
let mut temp = tempfile::Builder::new()
77-
.prefix(crate::TMP_PREFIX)
78-
.tempfile_in(dir)
79-
.map_err(context)?;
80-
if let Err(err) = temp.write_all(content) {
81-
let _ = temp.close();
82-
warn!("Failed to write {:?}: {:?}", relpath, err);
83-
return Err(context(err));
74+
let oops = |err| super::Error::io_error(&full_path, err);
75+
let mut options = File::options();
76+
options.write(true);
77+
match write_mode {
78+
WriteMode::CreateNew => {
79+
options.create_new(true);
80+
}
81+
WriteMode::Overwrite => {
82+
options.create(true).truncate(true);
83+
}
8484
}
85-
if let Err(persist_error) = temp.persist(&full_path) {
86-
warn!("Failed to persist {:?}: {:?}", full_path, persist_error);
87-
persist_error.file.close().map_err(context)?;
88-
Err(context(persist_error.error))
89-
} else {
90-
trace!("Wrote {} bytes", content.len());
91-
Ok(())
85+
let mut file = options.open(&full_path).map_err(oops)?;
86+
if let Err(err) = file.write_all(content) {
87+
error!("Failed to write {full_path:?}: {err:?}");
88+
drop(file);
89+
if let Err(err2) = remove_file(&full_path) {
90+
error!("Failed to remove {full_path:?}: {err2:?}");
91+
}
92+
return Err(oops(err));
9293
}
94+
trace!("Wrote {} bytes", content.len());
95+
Ok(())
9396
}
9497

9598
fn list_dir(&self, relpath: &str) -> Result<ListDir> {
@@ -257,7 +260,11 @@ mod test {
257260

258261
transport.create_dir("subdir").unwrap();
259262
transport
260-
.write_file("subdir/subfile", b"Must I paint you a picture?")
263+
.write_file(
264+
"subdir/subfile",
265+
b"Must I paint you a picture?",
266+
WriteMode::CreateNew,
267+
)
261268
.unwrap();
262269

263270
temp.child("subdir").assert(predicate::path::is_dir());
@@ -291,10 +298,10 @@ mod test {
291298
let transport = Transport::local(temp.path());
292299
let filename = "filename";
293300
transport
294-
.write_file(filename, b"original content")
301+
.write_file(filename, b"original content", WriteMode::Overwrite)
295302
.expect("first write succeeds");
296303
transport
297-
.write_file(filename, b"new content")
304+
.write_file(filename, b"new content", WriteMode::Overwrite)
298305
.expect("write over existing file succeeds");
299306
assert_eq!(
300307
transport.read_file(filename).unwrap().as_ref(),

src/transport/s3.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ use aws_types::SdkConfig;
4242
use base64::Engine;
4343
use bytes::Bytes;
4444
use tokio::runtime::Runtime;
45-
use tracing::{debug, trace, trace_span};
45+
use tracing::{debug, instrument, trace, trace_span};
4646
use url::Url;
4747

48-
use super::{Error, ErrorKind, Kind, ListDir, Metadata, Result};
48+
use super::{Error, ErrorKind, Kind, ListDir, Metadata, Result, WriteMode};
4949

5050
pub(super) struct Protocol {
5151
url: Url,
@@ -261,19 +261,22 @@ impl super::Protocol for Protocol {
261261
Ok(())
262262
}
263263

264-
fn write_file(&self, relpath: &str, content: &[u8]) -> Result<()> {
265-
let _span = trace_span!("S3Transport::write_file", %relpath).entered();
264+
#[instrument(skip(self, content))]
265+
fn write_file(&self, relpath: &str, content: &[u8], write_mode: WriteMode) -> Result<()> {
266266
let key = self.join_path(relpath);
267267
let crc32c =
268268
base64::engine::general_purpose::STANDARD.encode(crc32c::crc32c(content).to_be_bytes());
269-
let request = self
269+
let mut request = self
270270
.client
271271
.put_object()
272272
.bucket(&self.bucket)
273273
.key(&key)
274274
.storage_class(self.storage_class.clone())
275275
.checksum_crc32_c(crc32c)
276276
.body(content.to_owned().into());
277+
if write_mode == WriteMode::CreateNew {
278+
request = request.if_none_match("*");
279+
}
277280
let response = self.runtime.block_on(request.send());
278281
// trace!(?response);
279282
response.map_err(|err| s3_error(key, err))?;

src/transport/sftp.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use url::Url;
1414

1515
use crate::Kind;
1616

17-
use super::{Error, ErrorKind, ListDir, Result};
17+
use super::{Error, ErrorKind, ListDir, Result, WriteMode};
1818

1919
pub(super) struct Protocol {
2020
url: Url,
@@ -151,17 +151,37 @@ impl super::Protocol for Protocol {
151151
}
152152
}
153153

154-
fn write_file(&self, relpath: &str, content: &[u8]) -> Result<()> {
154+
fn write_file(&self, relpath: &str, content: &[u8], write_mode: WriteMode) -> Result<()> {
155155
let full_path = self.base_path.join(relpath);
156-
trace!("write_file {:>9} bytes to {:?}", content.len(), full_path);
157-
let mut file = self.sftp.create(&full_path).map_err(|err| {
158-
warn!(?err, ?relpath, "sftp error creating file");
159-
ssh_error(err, relpath)
160-
})?;
161-
file.write_all(content).map_err(|err| {
156+
trace!(
157+
"write_file {len:>9} bytes to {full_path:?}",
158+
len = content.len()
159+
);
160+
let flags = ssh2::OpenFlags::WRITE
161+
| ssh2::OpenFlags::CREATE
162+
| match write_mode {
163+
WriteMode::CreateNew => ssh2::OpenFlags::EXCLUSIVE,
164+
WriteMode::Overwrite => ssh2::OpenFlags::TRUNCATE,
165+
};
166+
let mut file = self
167+
.sftp
168+
.open_mode(&full_path, flags, 0o644, ssh2::OpenType::File)
169+
.map_err(|err| {
170+
warn!(?err, ?relpath, "sftp error creating file");
171+
ssh_error(err, relpath)
172+
})?;
173+
if let Err(err) = file.write_all(content) {
162174
warn!(?err, ?full_path, "sftp error writing file");
163-
io_error(err, relpath)
164-
})
175+
if let Err(err2) = self.sftp.unlink(&full_path) {
176+
warn!(
177+
?err2,
178+
?full_path,
179+
"sftp error unlinking file after write error"
180+
);
181+
}
182+
return Err(super::Error::io_error(&full_path, err));
183+
}
184+
Ok(())
165185
}
166186

167187
fn metadata(&self, relpath: &str) -> Result<super::Metadata> {

tests/format_flags.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use conserve::test_fixtures::ScratchArchive;
77
use conserve::*;
8+
use transport::WriteMode;
89

910
#[test]
1011
// This can be updated if/when Conserve does start writing some flags by default.
@@ -43,7 +44,11 @@ fn unknown_format_flag_fails_to_open() {
4344
});
4445
af.transport()
4546
.chdir("b0000")
46-
.write_file("BANDHEAD", &serde_json::to_vec(&head).unwrap())
47+
.write_file(
48+
"BANDHEAD",
49+
&serde_json::to_vec(&head).unwrap(),
50+
WriteMode::CreateNew,
51+
)
4752
.unwrap();
4853

4954
let err = Band::open(&af, BandId::zero()).unwrap_err();

0 commit comments

Comments
 (0)