extremedb/device/
util.rs

1// util.rs
2//
3// This file is a part of the eXtremeDB source code
4// Copyright (c) 2020 McObject LLC
5// All Rights Reserved
6
7//! Device utilities.
8//!
9//! This module is currently intended to be used by doctests only. It will be
10//! compiled conditionally when (if) Rust [issue 67295] is fixed.
11//!
12//! [issue 67295]: https://github.com/rust-lang/rust/issues/67295
13
14use std::env;
15use std::fs;
16use std::path::Path;
17
18use crate::device::{Assignment, Device, FileOpenFlags, NamedMemFlags};
19use crate::{runtime::Runtime, Result};
20
21#[cfg(target_os = "macos")]
22const MCO_DATABASE_DEFAULT_MAP_ADDRESS: usize = 0x2_0000_0000;
23
24#[cfg(not(target_os = "macos"))]
25const MCO_DATABASE_DEFAULT_MAP_ADDRESS: usize = 0x2000_0000;
26
27const MAX_SHMEM_NAME_LEN: usize = 32;
28const MAX_FILE_NAME_LEN: usize = 128;
29
30/// A device container.
31///
32/// Creates and maintains a list of devices depending on the current runtime
33/// configuration. Cleans up database files when dropped.
34///
35/// This type is not designed for production use. It will panic on failures.
36pub struct DeviceContainer {
37    devs: Vec<Device>,
38}
39
40impl DeviceContainer {
41    /// Creates a new device container for the current runtime configuration.
42    pub fn new() -> Self {
43        let rt_info = Runtime::info_impl();
44
45        let mut ret = DeviceContainer { devs: Vec::new() };
46
47        if rt_info.disk_supported() {
48            let stem = "rstest";
49
50            // Delete any stale files.
51            remove_db_files(&stem);
52
53            ret.devs.push(new_test_mem_dev(Assignment::Database));
54            ret.devs.push(new_test_mem_dev(Assignment::Cache));
55            ret.devs.push(new_test_file_dev(
56                Assignment::Persistent,
57                Some(db_file_name(&stem)),
58            ));
59            ret.devs.push(new_test_file_dev(
60                Assignment::Log,
61                Some(log_file_name(&stem)),
62            ));
63        } else {
64            ret.devs.push(new_test_mem_dev(Assignment::Database));
65        }
66
67        ret
68    }
69
70    /// Returns a reference to the contained devices.
71    pub fn devices(&mut self) -> &mut Vec<Device> {
72        &mut self.devs
73    }
74}
75
76impl Drop for DeviceContainer {
77    fn drop(&mut self) {
78        for dev in &self.devs {
79            if let Some(file_name) = dev.file_name() {
80                fs::remove_file(file_name)
81                    .expect(&format!("Failed to remove database file {}", file_name));
82            }
83        }
84    }
85}
86
87/// Creates a new named memory device if shared memory is supported,
88/// conventional memory device otherwise.
89pub fn new_mem_dev(a: Assignment, s: usize, name: &str) -> Result<Device> {
90    let info = Runtime::info_impl();
91
92    if info.multiprocess_access_supported() {
93        let hint = if info.direct_pointers_supported() {
94            MCO_DATABASE_DEFAULT_MAP_ADDRESS
95        } else {
96            0usize
97        };
98        Device::new_mem_named(a, s, name, NamedMemFlags::new(), hint)
99    } else {
100        Device::new_mem_conv(a, s)
101    }
102}
103
104/// Creates a new memory device which must be suitable for most doctests.
105///
106/// For named devices, the name is inferred from the executable name.
107///
108/// # Panics
109///
110/// This function will panic if the device cannot be created, or the name of
111/// the shared memory segment cannot be inferred.
112pub fn new_test_mem_dev(a: Assignment) -> Device {
113    new_mem_dev(a, 1 * 1024 * 1024, &mem_dev_name(a))
114        .expect("Failed to create the default memory device")
115}
116
117/// Creates a new file device which must be suitable for most doctests.
118///
119/// If `name` is `None`, the name of the file is inferred from the executable
120/// name.
121///
122/// # Panics
123///
124/// This function will panic if the device cannot be created, or the name of
125/// the file cannot be produced.
126pub fn new_test_file_dev(a: Assignment, name: Option<String>) -> Device {
127    let file_name = name.unwrap_or_else(|| file_name(&db_file_name_stem(), a));
128
129    Device::new_file(a, FileOpenFlags::new(), &file_name)
130        .expect("Failed to create the default disk device")
131}
132
133fn exec_name() -> String {
134    let path = env::args_os().next().expect("Unknown executable path");
135    let name = Path::new(&path)
136        .file_name()
137        .expect("Unknown executable name");
138    name.to_str().expect("Invalid executable name").to_string()
139}
140
141fn mem_dev_name(a: Assignment) -> String {
142    let mut name = exec_name();
143    name.truncate(MAX_SHMEM_NAME_LEN);
144    match a {
145        Assignment::Database => format!("{}-db", name),
146        Assignment::Cache => format!("{}-cache", name),
147        _ => panic!("Unexpected memory device assignment"),
148    }
149}
150
151fn db_file_name_stem() -> String {
152    let mut exec = exec_name();
153    exec.truncate(MAX_FILE_NAME_LEN);
154    let mut stem = exec.clone();
155    let mut i = 1usize;
156
157    while Path::new(&db_file_name(&stem)).exists() || Path::new(&log_file_name(&stem)).exists() {
158        stem = format!("{}-{}", exec, i);
159        i += 1;
160    }
161
162    stem
163}
164
165fn file_name(stem: &str, a: Assignment) -> String {
166    match a {
167        Assignment::Persistent => db_file_name(stem),
168        Assignment::Log => log_file_name(stem),
169        _ => panic!("Unexpected file device assignment"),
170    }
171}
172
173fn db_file_name(stem: &str) -> String {
174    format!("{}.dbs", stem)
175}
176
177fn log_file_name(stem: &str) -> String {
178    format!("{}.log", stem)
179}
180
181fn remove_db_files(stem: &str) {
182    for a in &[Assignment::Persistent, Assignment::Log] {
183        let name = file_name(stem, *a);
184        if Path::new(&name).exists() {
185            fs::remove_file(&name).expect(&format!("Failed to remove {}", name));
186        }
187    }
188}