cube(2): CUBE_OP_GET answers with the record's class
The last hole in the substrate: a caller could write a class through the syscall but not read one back. `CUBE_OP_GET` now fills `args.flags` from the same index entry the address came from, so learning what a record *is* costs nothing beyond a read that was going to happen. One field for both directions, because it is one thing — the class of this record. A write states it, a read learns it, and neither is a special case of the other. `find` hands the mask back with the address for the same reason: it is in the same stride the binary search already read, so wanting both does not mean searching twice. A read that finds nothing leaves 0 rather than a stale class for the caller to believe. The size of `cube_args` does not change, which matters because `size` is what says which argument block arrived.
This commit is contained in:
@@ -32,7 +32,7 @@
|
||||
int cubelinux_kernel_put(const __u8 *space, __u64 x, __u64 y, __u64 z,
|
||||
const void *value, size_t len, __u16 flags);
|
||||
ssize_t cubelinux_kernel_get(const __u8 *space, __u64 x, __u64 y, __u64 z,
|
||||
void *buf, size_t len);
|
||||
void *buf, size_t len, __u16 *out_flags);
|
||||
int cubelinux_kernel_del(const __u8 *space, __u64 x, __u64 y, __u64 z);
|
||||
int cubelinux_kernel_sync(void);
|
||||
|
||||
@@ -196,7 +196,8 @@ static long cube_args_op(unsigned int op, void __user *uargs)
|
||||
ssize_t got;
|
||||
|
||||
got = cubelinux_kernel_get(args.coord.space, args.coord.x,
|
||||
args.coord.y, args.coord.z, buf, args.len);
|
||||
args.coord.y, args.coord.z, buf, args.len,
|
||||
&args.flags);
|
||||
if (got < 0) {
|
||||
ret = got;
|
||||
break;
|
||||
|
||||
@@ -2105,7 +2105,8 @@ impl<'a> Records<'a> {
|
||||
|
||||
/// What the log says about one coordinate: written, or removed.
|
||||
enum Effect<'a> {
|
||||
Write(&'a [u8]),
|
||||
/// The bytes it wrote, and the class mask it wrote them under.
|
||||
Write(&'a [u8], u16),
|
||||
Delete,
|
||||
}
|
||||
|
||||
@@ -2116,12 +2117,12 @@ enum Effect<'a> {
|
||||
fn log_effect<'a>(log: &'a [u8], space: &[u8; SPACE_ID_LEN], key: &[u8; RAW_KEY_LEN]) -> Option<Effect<'a>> {
|
||||
let mut entries = log_entries(log).ok()?;
|
||||
let mut found = None;
|
||||
while let Some((entry_space, entry_key, op, _flags, value_at, value_len)) = entries.next() {
|
||||
while let Some((entry_space, entry_key, op, flags, value_at, value_len)) = entries.next() {
|
||||
if entry_space == space && entry_key == key {
|
||||
found = Some(if op == 2 {
|
||||
Effect::Delete
|
||||
} else {
|
||||
Effect::Write(&log[value_at..value_at + value_len])
|
||||
Effect::Write(&log[value_at..value_at + value_len], flags)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2771,8 +2772,17 @@ impl Addressed {
|
||||
Ok((value_off, u64::from_le_bytes(w), flags))
|
||||
}
|
||||
|
||||
/// The address of `key` inside a space's index range, or nothing if the space does not hold it.
|
||||
fn find(&mut self, key: &[u8; RAW_KEY_LEN], first: u64, records: u64) -> Result<Option<(u64, u64)>> {
|
||||
/// The address of `key` inside a space's index range, or nothing if the space does not hold it:
|
||||
/// `(value_off, value_len, flags)`.
|
||||
///
|
||||
/// The mask comes back with the address because it is in the same stride the search already
|
||||
/// read, so a read that wants both does not search twice for one entry.
|
||||
fn find(
|
||||
&mut self,
|
||||
key: &[u8; RAW_KEY_LEN],
|
||||
first: u64,
|
||||
records: u64,
|
||||
) -> Result<Option<(u64, u64, u16)>> {
|
||||
let mut buf = KVVec::<u8>::new();
|
||||
let mut lo = 0u64;
|
||||
let mut hi = records;
|
||||
@@ -2787,9 +2797,7 @@ impl Addressed {
|
||||
} else if found > &key[..] {
|
||||
hi = mid;
|
||||
} else {
|
||||
return self
|
||||
.index_entry(first + mid, &mut buf)
|
||||
.map(|(value_off, value_len, _)| Some((value_off, value_len)));
|
||||
return self.index_entry(first + mid, &mut buf).map(Some);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
@@ -2842,7 +2850,14 @@ impl Addressed {
|
||||
///
|
||||
/// # Safety
|
||||
/// `buf` must hold `len` writable bytes.
|
||||
unsafe fn v3_get(mut image: Addressed, sp: [u8; SPACE_ID_LEN], key: [u8; RAW_KEY_LEN], buf: *mut u8, len: usize) -> isize {
|
||||
unsafe fn v3_get(
|
||||
mut image: Addressed,
|
||||
sp: [u8; SPACE_ID_LEN],
|
||||
key: [u8; RAW_KEY_LEN],
|
||||
buf: *mut u8,
|
||||
len: usize,
|
||||
out_flags: *mut u16,
|
||||
) -> isize {
|
||||
// The log is owned for the duration: what it says borrows it, and reading the image needs
|
||||
// `&mut image`. It is what a checkpoint has not folded, so it is small.
|
||||
let mut log = KVVec::<u8>::new();
|
||||
@@ -2851,7 +2866,11 @@ unsafe fn v3_get(mut image: Addressed, sp: [u8; SPACE_ID_LEN], key: [u8; RAW_KEY
|
||||
}
|
||||
match log_effect(log.as_slice(), &sp, &key) {
|
||||
Some(Effect::Delete) => return -2, // -ENOENT
|
||||
Some(Effect::Write(value)) => return copy_out(value, buf, len),
|
||||
Some(Effect::Write(value, flags)) => {
|
||||
// SAFETY: the caller guarantees a writable u16.
|
||||
unsafe { *out_flags = flags };
|
||||
return copy_out(value, buf, len);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
let (first, records) = match image.space_entry(&sp) {
|
||||
@@ -2859,7 +2878,7 @@ unsafe fn v3_get(mut image: Addressed, sp: [u8; SPACE_ID_LEN], key: [u8; RAW_KEY
|
||||
Ok(None) => return -2,
|
||||
Err(e) => return -(e.to_errno() as isize),
|
||||
};
|
||||
let (value_off, value_len) = match image.find(&key, first, records) {
|
||||
let (value_off, value_len, flags) = match image.find(&key, first, records) {
|
||||
Ok(Some(entry)) => entry,
|
||||
Ok(None) => return -2,
|
||||
Err(e) => return -(e.to_errno() as isize),
|
||||
@@ -2868,6 +2887,10 @@ unsafe fn v3_get(mut image: Addressed, sp: [u8; SPACE_ID_LEN], key: [u8; RAW_KEY
|
||||
Ok(v) => v,
|
||||
Err(e) => return -(e.to_errno() as isize),
|
||||
};
|
||||
// The record's own class, from the index entry the address came from. A v3 entry has no mask
|
||||
// and answers zero, which is what "no class" means everywhere here.
|
||||
// SAFETY: the caller guarantees a writable u16.
|
||||
unsafe { *out_flags = flags };
|
||||
copy_out(value.as_slice(), buf, len)
|
||||
}
|
||||
|
||||
@@ -3615,12 +3638,17 @@ pub unsafe extern "C" fn cubelinux_kernel_get(
|
||||
z: u64,
|
||||
buf: *mut u8,
|
||||
len: usize,
|
||||
out_flags: *mut u16,
|
||||
) -> isize {
|
||||
let (sp, key) = unsafe { coord_key(space, x, y, z) };
|
||||
// A read that finds nothing must not leave a class behind for the caller to believe: the answer
|
||||
// is "no record", and a stale mask beside it would be worse than none.
|
||||
// SAFETY: the caller guarantees a writable u16.
|
||||
unsafe { *out_flags = 0 };
|
||||
// A v3 image is addressed, so a read is a binary search and a few bytes. v1 and v2 images are
|
||||
// packed lists, which the walking reader below handles.
|
||||
match Addressed::open() {
|
||||
Ok(Some(image)) => return unsafe { v3_get(image, sp, key, buf, len) },
|
||||
Ok(Some(image)) => return unsafe { v3_get(image, sp, key, buf, len, out_flags) },
|
||||
Ok(None) => {}
|
||||
Err(e) => return -(e.to_errno() as isize),
|
||||
}
|
||||
@@ -3631,7 +3659,11 @@ pub unsafe extern "C" fn cubelinux_kernel_get(
|
||||
|
||||
match log_effect(view.log(), &sp, &key) {
|
||||
Some(Effect::Delete) => return -2, // -ENOENT
|
||||
Some(Effect::Write(value)) => return copy_out(value, buf, len),
|
||||
Some(Effect::Write(value, flags)) => {
|
||||
// SAFETY: the caller guarantees a writable u16.
|
||||
unsafe { *out_flags = flags };
|
||||
return copy_out(value, buf, len);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
|
||||
+14
-11
@@ -34,18 +34,21 @@ struct cube_args {
|
||||
* out: on -ERANGE, the bytes that would be needed;
|
||||
* on success for CUBE_OP_GET, the bytes read.
|
||||
*/
|
||||
__u16 flags; /* in: CUBE_OP_PUT only — the class mask to stamp on the
|
||||
* record. The mask is written where the record is written,
|
||||
* which is the moment its class is known for certain; a
|
||||
* reader never has to re-derive it. 0 is "no class", which
|
||||
* is what every record written before the field existed
|
||||
* reads as, so a caller that does not classify is not
|
||||
* writing a special value.
|
||||
__u16 flags; /* in: CUBE_OP_PUT — the class mask to stamp on the record;
|
||||
* out: CUBE_OP_GET — the mask the record carries.
|
||||
*
|
||||
* A store whose image is the legacy packed layout has no
|
||||
* field to put a mask in, and drops it: that layout cannot
|
||||
* carry a class and saying otherwise would be a lie about
|
||||
* the bytes on disk.
|
||||
* One field for both directions because it is one thing: the
|
||||
* class of this record. A write states it, a read learns it,
|
||||
* and neither is a special case of the other. 0 is "no class",
|
||||
* which is what every record written before the field existed
|
||||
* reads as — so not classifying is not writing a special value,
|
||||
* and a read that found nothing leaves 0 rather than a stale
|
||||
* class for the caller to believe.
|
||||
*
|
||||
* A store whose image is the legacy packed layout has no field
|
||||
* to put a mask in, and drops it: that layout cannot carry a
|
||||
* class, and saying otherwise would be a lie about the bytes on
|
||||
* disk. A read from such a store answers 0 for the same reason.
|
||||
*/
|
||||
__u16 reserved; /* must be 0 */
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user