aws_manager/ec2/
disk.rs

1use std::{
2    fs::{self, File},
3    io::{self, Write},
4    thread, time,
5};
6
7/// Makes a new file system on the specified device.
8///
9/// e.g.,
10/// sudo mkfs -t ext4 /dev/nvme1n1
11///
12/// Usually, "nvme0n1" is the boot volume.
13/// "nvme1n1" is the additional mounted volume.
14///
15/// ref. See https://github.com/cholcombe973/block-utils/blob/master/src/lib.rs for other commands.
16/// ref. https://stackoverflow.com/questions/45167717/mounting-a-nvme-disk-on-aws-ec2
17pub fn make_filesystem(
18    filesystem_name: &str,
19    device_name: &str,
20) -> io::Result<command_manager::Output> {
21    match run_make_filesystem(filesystem_name, device_name) {
22        Ok(v) => {
23            log::debug!("first time success 'make_filesystem'");
24            return Ok(v);
25        }
26        Err(e) => {
27            log::warn!(
28                "first time failure 'make_filesystem' {}; retrying after 5-second...",
29                e
30            );
31            thread::sleep(time::Duration::from_secs(5));
32            return run_make_filesystem(filesystem_name, device_name);
33        }
34    }
35}
36
37pub fn run_make_filesystem(
38    filesystem_name: &str,
39    device_name: &str,
40) -> io::Result<command_manager::Output> {
41    let device_path = if device_name.starts_with("/dev/") {
42        device_name.to_string()
43    } else {
44        format!("/dev/{}", device_name)
45    };
46
47    log::info!(
48        "making file system with 'mkfs' command on the device path '{}'",
49        device_path
50    );
51
52    let cmd = format!("sudo mkfs -t {} {}", filesystem_name, device_path);
53    let res = command_manager::run(&cmd);
54    if res.is_err() {
55        // e.g., mke2fs 1.45.5 (07-Jan-2020) /dev/nvme1n1 is mounted; will not make a filesystem here!
56        // TODO: handle "mount: /data: wrong fs type, bad option, bad superblock on /dev/nvme1n1, missing codepage or helper program, or other error."
57        let e = res.err().unwrap();
58        if !e
59            .to_string()
60            .contains(format!("{} is mounted", device_path).as_str())
61        {
62            return Err(e);
63        }
64
65        log::warn!("ignoring the 'is mounted' error '{}'", e.to_string());
66        Ok(command_manager::Output {
67            stdout: String::new(),
68            stderr: e.to_string(),
69        })
70    } else {
71        res
72    }
73}
74
75/// Mounts the file system to the specified directory.
76/// And updates "/etc/fstab" to auto remount in case of instance reboot.
77///
78/// e.g.,
79/// sudo mount /dev/nvme1n1 /data -t ext4
80///
81/// ref. See https://github.com/cholcombe973/block-utils/blob/master/src/lib.rs for other commands.
82/// ref. https://stackoverflow.com/questions/45167717/mounting-a-nvme-disk-on-aws-ec2
83pub fn mount_filesystem(
84    filesystem_name: &str,
85    device_name: &str,
86    dir_name: &str,
87) -> io::Result<command_manager::Output> {
88    let device_path = if device_name.starts_with("/dev/") {
89        device_name.to_string()
90    } else {
91        format!("/dev/{}", device_name)
92    };
93
94    log::info!(
95        "mounting the file system with 'mount' command on the device path '{}'",
96        device_path
97    );
98
99    let cmd = format!(
100        "sudo mount {} {} -t {}",
101        device_path, dir_name, filesystem_name
102    );
103    let res = command_manager::run(&cmd);
104    if res.is_err() {
105        // e.g., mount: /data: /dev/nvme1n1 already mounted on /data
106        let e = res.err().unwrap();
107        if !e
108            .to_string()
109            .contains(format!("{} already mounted", device_path).as_str())
110        {
111            return Err(e);
112        }
113
114        log::warn!("ignoring the 'already mounted' error '{}'", e.to_string());
115        Ok(command_manager::Output {
116            stdout: String::new(),
117            stderr: e.to_string(),
118        })
119    } else {
120        res
121    }
122}
123
124const FSTAB_PATH: &str = "/etc/fstab";
125
126/// Updates "/etc/fstab" to auto remount in case of instance reboot.
127/// The output is the contents of the "fstab" file.
128///
129/// e.g.,
130/// sudo echo '/dev/nvme1n1       /data   ext4    defaults,nofail 0       2' >> /etc/fstab
131/// sudo mount --all
132///
133/// ref. See https://github.com/cholcombe973/block-utils/blob/master/src/lib.rs for other commands.
134/// ref. https://stackoverflow.com/questions/45167717/mounting-a-nvme-disk-on-aws-ec2
135pub fn update_fstab(
136    filesystem_name: &str,
137    device_name: &str,
138    dir_name: &str,
139) -> io::Result<command_manager::Output> {
140    let device_path = if device_name.starts_with("/dev/") {
141        device_name.to_string()
142    } else {
143        format!("/dev/{}", device_name)
144    };
145
146    log::info!(
147        "updating the fstab file on the device path '{}'",
148        device_path
149    );
150
151    let line = format!(
152        "{}       {}   {}    defaults,nofail 0       2",
153        device_path, dir_name, filesystem_name
154    );
155    let mut contents = fs::read_to_string(FSTAB_PATH)?;
156    if contents.contains(&line) {
157        log::warn!("fstab already contains '{}', skipping updating fstab", line);
158        return Ok(command_manager::Output {
159            stdout: contents,
160            stderr: String::new(),
161        });
162    }
163    contents.push('\n');
164    contents.push_str(&line);
165
166    let tmp_path = random_manager::tmp_path(10, None)?;
167    let mut f = File::create(&tmp_path)?;
168    f.write_all(contents.as_bytes())?;
169
170    let cmd = format!("sudo cp {} {}", tmp_path, FSTAB_PATH);
171    command_manager::run(&cmd)?;
172    command_manager::run("sudo mount --all")?;
173
174    Ok(command_manager::Output {
175        stdout: contents,
176        stderr: String::new(),
177    })
178}