diff --git a/src/api/s3/copy.rs b/src/api/s3/copy.rs index 33a0cdd1..7ee1847d 100644 --- a/src/api/s3/copy.rs +++ b/src/api/s3/copy.rs @@ -148,14 +148,18 @@ pub async fn handle_copy( && (was_multipart || checksum_algorithm != source_checksum_algorithm)); let res = if !must_recopy { + let dest_info = DestInfo { + key: dest_key, + uuid: dest_uuid, + object_meta: dest_object_meta, + encryption: dest_encryption, + }; + // In most cases, we can just copy the metadata and link blocks of the // old object from the new object. handle_copy_metaonly( ctx, - dest_key, - dest_uuid, - dest_object_meta, - dest_encryption, + dest_info, source_version, source_version_data, source_version_meta, @@ -177,12 +181,16 @@ pub async fn handle_copy( checksum_type: checksum_algorithm.map(|_| ChecksumType::FullObject), ..dest_object_meta }; + + let dest_info = DestInfo { + key: dest_key, + uuid: dest_uuid, + object_meta: dest_object_meta, + encryption: dest_encryption, + }; handle_copy_reencrypt( ctx, - dest_key, - dest_uuid, - dest_object_meta, - dest_encryption, + dest_info, source_version, source_version_data, source_encryption, @@ -209,12 +217,16 @@ pub async fn handle_copy( Ok(resp.body(string_body(xml))?) } +struct DestInfo<'a> { + key: &'a str, + uuid: Uuid, + object_meta: ObjectVersionMetaInner, + encryption: EncryptionParams, +} + async fn handle_copy_metaonly( ctx: ReqCtx, - dest_key: &str, - dest_uuid: Uuid, - dest_object_meta: ObjectVersionMetaInner, - dest_encryption: EncryptionParams, + dest_info: DestInfo<'_>, source_version: &ObjectVersion, source_version_data: &ObjectVersionData, source_version_meta: &ObjectVersionMeta, @@ -229,13 +241,13 @@ async fn handle_copy_metaonly( let new_timestamp = now_msec(); let new_meta = ObjectVersionMeta { - encryption: dest_encryption.encrypt_meta(dest_object_meta)?, + encryption: dest_info.encryption.encrypt_meta(dest_info.object_meta)?, size: source_version_meta.size, etag: source_version_meta.etag.clone(), }; let res = SaveStreamResult { - version_uuid: dest_uuid, + version_uuid: dest_info.uuid, version_timestamp: new_timestamp, etag: new_meta.etag.clone(), }; @@ -247,7 +259,7 @@ async fn handle_copy_metaonly( // bytes is either plaintext before&after or encrypted with the // same keys, so it's ok to just copy it as is let dest_object_version = ObjectVersion { - uuid: dest_uuid, + uuid: dest_info.uuid, timestamp: new_timestamp, state: ObjectVersionState::Complete(ObjectVersionData::Inline( new_meta, @@ -256,7 +268,7 @@ async fn handle_copy_metaonly( }; let dest_object = Object::new( dest_bucket_id, - dest_key.to_string(), + dest_info.key.to_string(), vec![dest_object_version], ); garage.object_table.insert(&dest_object).await?; @@ -274,7 +286,7 @@ async fn handle_copy_metaonly( // This holds a reference to the object in the Version table // so that it won't be deleted, e.g. by repair_versions. let tmp_dest_object_version = ObjectVersion { - uuid: dest_uuid, + uuid: dest_info.uuid, timestamp: new_timestamp, state: ObjectVersionState::Uploading { encryption: new_meta.encryption.clone(), @@ -284,11 +296,13 @@ async fn handle_copy_metaonly( }; let tmp_dest_object = Object::new( dest_bucket_id, - dest_key.to_string(), + dest_info.key.to_string(), vec![tmp_dest_object_version], ); garage.object_table.insert(&tmp_dest_object).await?; + let dest_uuid = dest_info.uuid; + // Write version in the version table. Even with empty block list, // this means that the BlockRef entries linked to this version cannot be // marked as deleted (they are marked as deleted only if the Version @@ -297,7 +311,7 @@ async fn handle_copy_metaonly( dest_uuid, VersionBacklink::Object { bucket_id: dest_bucket_id, - key: dest_key.to_string(), + key: dest_info.key.to_string(), }, false, ); @@ -329,7 +343,7 @@ async fn handle_copy_metaonly( // with the stuff before, the block's reference counts could be decremented before // they are incremented again for the new version, leading to data being deleted. let dest_object_version = ObjectVersion { - uuid: dest_uuid, + uuid: dest_info.uuid, timestamp: new_timestamp, state: ObjectVersionState::Complete(ObjectVersionData::FirstBlock( new_meta, @@ -338,7 +352,7 @@ async fn handle_copy_metaonly( }; let dest_object = Object::new( dest_bucket_id, - dest_key.to_string(), + dest_info.key.to_string(), vec![dest_object_version], ); garage.object_table.insert(&dest_object).await?; @@ -350,10 +364,7 @@ async fn handle_copy_metaonly( async fn handle_copy_reencrypt( ctx: ReqCtx, - dest_key: &str, - dest_uuid: Uuid, - dest_object_meta: ObjectVersionMetaInner, - dest_encryption: EncryptionParams, + dest_info: DestInfo<'_>, source_version: &ObjectVersion, source_version_data: &ObjectVersionData, source_encryption: EncryptionParams, @@ -371,11 +382,11 @@ async fn handle_copy_reencrypt( save_stream( &ctx, - dest_uuid, - dest_object_meta, - dest_encryption, + dest_info.uuid, + dest_info.object_meta, + dest_info.encryption, source_stream.map_err(|e| Error::from(GarageError::from(e))), - &dest_key.to_string(), + &dest_info.key.to_string(), checksum_mode, ) .await diff --git a/src/api/s3/get.rs b/src/api/s3/get.rs index 5a427a87..3c07bc2f 100644 --- a/src/api/s3/get.rs +++ b/src/api/s3/get.rs @@ -318,18 +318,22 @@ pub async fn handle_get_without_ctx( let checksum_mode = checksum_mode(req); + let handle_get_info = HandleGetInfo { + garage, + version: last_v, + version_data: last_v_data, + version_meta: last_v_meta, + encryption: enc, + meta_inner: &headers, + }; + match (part_number, parse_range_header(req, last_v_meta.size)?) { (Some(_), Some(_)) => Err(Error::bad_request( "Cannot specify both partNumber and Range header", )), (Some(pn), None) => { handle_get_part( - garage, - last_v, - last_v_data, - last_v_meta, - enc, - &headers, + handle_get_info, pn, ChecksumMode { // TODO: for multipart uploads, checksums of each part should be stored @@ -342,12 +346,7 @@ pub async fn handle_get_without_ctx( } (None, Some(range)) => { handle_get_range( - garage, - last_v, - last_v_data, - last_v_meta, - enc, - &headers, + handle_get_info, range.start, range.start + range.length, ChecksumMode { @@ -359,19 +358,7 @@ pub async fn handle_get_without_ctx( ) .await } - (None, None) => { - handle_get_full( - garage, - last_v, - last_v_data, - last_v_meta, - enc, - &headers, - overrides, - checksum_mode, - ) - .await - } + (None, None) => handle_get_full(handle_get_info, overrides, checksum_mode).await, } } @@ -390,23 +377,37 @@ pub(crate) fn check_version_not_deleted(version: &Version) -> Result<(), Error> Ok(()) } -async fn handle_get_full( +struct HandleGetInfo<'a> { garage: Arc, - version: &ObjectVersion, - version_data: &ObjectVersionData, - version_meta: &ObjectVersionMeta, + version: &'a ObjectVersion, + version_data: &'a ObjectVersionData, + version_meta: &'a ObjectVersionMeta, encryption: EncryptionParams, - meta_inner: &ObjectVersionMetaInner, + meta_inner: &'a ObjectVersionMetaInner, +} + +async fn handle_get_full( + info: HandleGetInfo<'_>, overrides: GetObjectOverrides, checksum_mode: ChecksumMode, ) -> Result, Error> { - let mut resp_builder = - object_headers(version, version_meta, meta_inner, encryption, checksum_mode) - .header(CONTENT_LENGTH, format!("{}", version_meta.size)) - .status(StatusCode::OK); + let mut resp_builder = object_headers( + info.version, + info.version_meta, + info.meta_inner, + info.encryption, + checksum_mode, + ) + .header(CONTENT_LENGTH, format!("{}", info.version_meta.size)) + .status(StatusCode::OK); getobject_override_headers(overrides, &mut resp_builder)?; - let stream = full_object_byte_stream(garage, version, version_data, encryption); + let stream = full_object_byte_stream( + info.garage, + info.version, + info.version_data, + info.encryption, + ); Ok(resp_builder.body(response_body_from_stream(stream))?) } @@ -486,12 +487,7 @@ pub fn full_object_byte_stream( } async fn handle_get_range( - garage: Arc, - version: &ObjectVersion, - version_data: &ObjectVersionData, - version_meta: &ObjectVersionMeta, - encryption: EncryptionParams, - meta_inner: &ObjectVersionMetaInner, + info: HandleGetInfo<'_>, begin: u64, end: u64, checksum_mode: ChecksumMode, @@ -499,18 +495,24 @@ async fn handle_get_range( // Here we do not use getobject_override_headers because we don't // want to add any overridden headers (those should not be added // when returning PARTIAL_CONTENT) - let resp_builder = object_headers(version, version_meta, meta_inner, encryption, checksum_mode) - .header(CONTENT_LENGTH, format!("{}", end - begin)) - .header( - CONTENT_RANGE, - format!("bytes {}-{}/{}", begin, end - 1, version_meta.size), - ) - .status(StatusCode::PARTIAL_CONTENT); + let resp_builder = object_headers( + info.version, + info.version_meta, + info.meta_inner, + info.encryption, + checksum_mode, + ) + .header(CONTENT_LENGTH, format!("{}", end - begin)) + .header( + CONTENT_RANGE, + format!("bytes {}-{}/{}", begin, end - 1, info.version_meta.size), + ) + .status(StatusCode::PARTIAL_CONTENT); - match &version_data { + match &info.version_data { ObjectVersionData::DeleteMarker => unreachable!(), ObjectVersionData::Inline(_meta, bytes) => { - let bytes = encryption.decrypt_blob(bytes)?; + let bytes = info.encryption.decrypt_blob(bytes)?; if end as usize <= bytes.len() { let body = bytes_body(bytes[begin as usize..end as usize].to_vec().into()); Ok(resp_builder.body(body)?) @@ -521,46 +523,47 @@ async fn handle_get_range( } } ObjectVersionData::FirstBlock(_meta, _first_block_hash) => { - let version = garage + let version = info + .garage .version_table - .get(&version.uuid, &EmptyKey) + .get(&info.version.uuid, &EmptyKey) .await? .ok_or(Error::NoSuchKey)?; check_version_not_deleted(&version)?; - let body = - body_from_blocks_range(garage, encryption, version.blocks.items(), begin, end); + let body = body_from_blocks_range( + info.garage, + info.encryption, + version.blocks.items(), + begin, + end, + ); Ok(resp_builder.body(body)?) } } } async fn handle_get_part( - garage: Arc, - object_version: &ObjectVersion, - version_data: &ObjectVersionData, - version_meta: &ObjectVersionMeta, - encryption: EncryptionParams, - meta_inner: &ObjectVersionMetaInner, + info: HandleGetInfo<'_>, part_number: u64, checksum_mode: ChecksumMode, ) -> Result, Error> { // Same as for get_range, no getobject_override_headers let resp_builder = object_headers( - object_version, - version_meta, - meta_inner, - encryption, + info.version, + info.version_meta, + info.meta_inner, + info.encryption, checksum_mode, ) .status(StatusCode::PARTIAL_CONTENT); - match version_data { + match info.version_data { ObjectVersionData::Inline(_, bytes) => { if part_number != 1 { return Err(Error::InvalidPart); } - let bytes = encryption.decrypt_blob(bytes)?; - assert_eq!(bytes.len() as u64, version_meta.size); + let bytes = info.encryption.decrypt_blob(bytes)?; + assert_eq!(bytes.len() as u64, info.version_meta.size); Ok(resp_builder .header(CONTENT_LENGTH, format!("{}", bytes.len())) .header( @@ -571,9 +574,10 @@ async fn handle_get_part( .body(bytes_body(bytes.into_owned().into()))?) } ObjectVersionData::FirstBlock(_, _) => { - let version = garage + let version = info + .garage .version_table - .get(&object_version.uuid, &EmptyKey) + .get(&info.version.uuid, &EmptyKey) .await? .ok_or(Error::NoSuchKey)?; @@ -582,14 +586,19 @@ async fn handle_get_part( let (begin, end) = calculate_part_bounds(&version, part_number).ok_or(Error::InvalidPart)?; - let body = - body_from_blocks_range(garage, encryption, version.blocks.items(), begin, end); + let body = body_from_blocks_range( + info.garage, + info.encryption, + version.blocks.items(), + begin, + end, + ); Ok(resp_builder .header(CONTENT_LENGTH, format!("{}", end - begin)) .header( CONTENT_RANGE, - format!("bytes {}-{}/{}", begin, end - 1, version_meta.size), + format!("bytes {}-{}/{}", begin, end - 1, info.version_meta.size), ) .header(X_AMZ_MP_PARTS_COUNT, format!("{}", version.n_parts()?)) .body(body)?)