diff --git a/native/src/base/cxx_extern.rs b/native/src/base/cxx_extern.rs index bdcbc133f..2070c9a20 100644 --- a/native/src/base/cxx_extern.rs +++ b/native/src/base/cxx_extern.rs @@ -1,14 +1,18 @@ // Functions listed here are just to export to C++ -use crate::{fd_path, mkdirs, realpath, rm_rf, slice_from_ptr_mut, Directory, ResultExt}; +use std::ffi::CStr; +use std::io; +use std::os::fd::{BorrowedFd, OwnedFd, RawFd}; + use anyhow::Context; use cxx::private::c_char; use libc::mode_t; -use std::ffi::CStr; -use std::io; -use std::os::fd::{OwnedFd, RawFd}; -pub fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize { +use crate::{ + fd_path, map_fd, map_file, mkdirs, realpath, rm_rf, slice_from_ptr_mut, Directory, ResultExt, +}; + +pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize { fd_path(fd, buf) .context("fd_path failed") .log() @@ -37,3 +41,19 @@ unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool { } inner(fd).map_or(false, |_| true) } + +pub(crate) fn map_file_for_cxx(path: &[u8], rw: bool) -> &'static mut [u8] { + unsafe { + map_file(CStr::from_bytes_with_nul_unchecked(path), rw) + .log() + .unwrap_or(&mut []) + } +} + +pub(crate) fn map_fd_for_cxx(fd: RawFd, sz: usize, rw: bool) -> &'static mut [u8] { + unsafe { + map_fd(BorrowedFd::borrow_raw(fd), sz, rw) + .log() + .unwrap_or(&mut []) + } +} diff --git a/native/src/base/files.cpp b/native/src/base/files.cpp index 7532ea525..6829de266 100644 --- a/native/src/base/files.cpp +++ b/native/src/base/files.cpp @@ -375,29 +375,19 @@ sFILE make_file(FILE *fp) { } mmap_data::mmap_data(const char *name, bool rw) { - int fd = xopen(name, (rw ? O_RDWR : O_RDONLY) | O_CLOEXEC); - if (fd < 0) - return; - - run_finally g([=] { close(fd); }); - struct stat st{}; - if (fstat(fd, &st)) - return; - if (S_ISBLK(st.st_mode)) { - uint64_t size; - ioctl(fd, BLKGETSIZE64, &size); - init(fd, size, rw); - } else { - init(fd, st.st_size, rw); + auto slice = rust::map_file(byte_view(name), rw); + if (!slice.empty()) { + _buf = slice.data(); + _sz = slice.size(); } } -void mmap_data::init(int fd, size_t sz, bool rw) { - _sz = sz; - void *b = sz > 0 - ? xmmap(nullptr, sz, PROT_READ | PROT_WRITE, rw ? MAP_SHARED : MAP_PRIVATE, fd, 0) - : nullptr; - _buf = static_cast(b); +mmap_data::mmap_data(int fd, size_t sz, bool rw) { + auto slice = rust::map_fd(fd, sz, rw); + if (!slice.empty()) { + _buf = slice.data(); + _sz = slice.size(); + } } mmap_data::~mmap_data() { diff --git a/native/src/base/files.hpp b/native/src/base/files.hpp index 01a0130cf..043d46847 100644 --- a/native/src/base/files.hpp +++ b/native/src/base/files.hpp @@ -45,10 +45,8 @@ struct mmap_data : public byte_data { ALLOW_MOVE_ONLY(mmap_data) explicit mmap_data(const char *name, bool rw = false); - mmap_data(int fd, size_t sz, bool rw = false) { init(fd, sz, rw); } + mmap_data(int fd, size_t sz, bool rw = false); ~mmap_data(); -private: - void init(int fd, size_t sz, bool rw); }; extern "C" { diff --git a/native/src/base/files.rs b/native/src/base/files.rs index b686e7346..970c23a87 100644 --- a/native/src/base/files.rs +++ b/native/src/base/files.rs @@ -7,20 +7,16 @@ use std::marker::PhantomData; use std::ops::Deref; use std::os::fd::{AsFd, BorrowedFd, IntoRawFd}; use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd}; -use std::{io, mem, slice}; +use std::{io, mem, ptr, slice}; -use libc::{c_char, c_uint, dirent, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH, O_RDONLY}; +use libc::{c_char, c_uint, dirent, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH, O_RDONLY, O_RDWR}; -use crate::{bfmt_cstr, copy_cstr, cstr, errno, error}; +use crate::{bfmt_cstr, copy_cstr, cstr, errno, error, LibcReturn}; pub fn __open_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> io::Result { unsafe { - let fd = libc::open(path.as_ptr(), flags, mode as c_uint); - if fd >= 0 { - Ok(OwnedFd::from_raw_fd(fd)) - } else { - Err(io::Error::last_os_error()) - } + let fd = libc::open(path.as_ptr(), flags, mode as c_uint).check_os_err()?; + Ok(OwnedFd::from_raw_fd(fd)) } } @@ -43,10 +39,8 @@ pub unsafe fn readlink_unsafe(path: *const c_char, buf: *mut u8, bufsz: usize) - } pub fn readlink(path: &CStr, data: &mut [u8]) -> io::Result { - let r = unsafe { readlink_unsafe(path.as_ptr(), data.as_mut_ptr(), data.len()) }; - if r < 0 { - return Err(io::Error::last_os_error()); - } + let r = + unsafe { readlink_unsafe(path.as_ptr(), data.as_mut_ptr(), data.len()) }.check_os_err()?; Ok(r as usize) } @@ -229,9 +223,7 @@ impl DirEntry<'_> { pub fn unlink(&self) -> io::Result<()> { let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 }; unsafe { - if libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag) < 0 { - return Err(io::Error::last_os_error()); - } + libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag).check_os_err()?; } Ok(()) } @@ -245,10 +237,8 @@ impl DirEntry<'_> { self.dir.as_raw_fd(), self.d_name.as_ptr(), O_RDONLY | O_CLOEXEC, - ); - if fd < 0 { - return Err(io::Error::last_os_error()); - } + ) + .check_os_err()?; Directory::try_from(OwnedFd::from_raw_fd(fd)) } } @@ -262,10 +252,8 @@ impl DirEntry<'_> { self.dir.as_raw_fd(), self.d_name.as_ptr(), flags | O_CLOEXEC, - ); - if fd < 0 { - return Err(io::Error::last_os_error()); - } + ) + .check_os_err()?; Ok(File::from_raw_fd(fd)) } } @@ -292,10 +280,7 @@ pub enum WalkResult { impl<'a> Directory<'a> { pub fn open(path: &CStr) -> io::Result { - let dirp = unsafe { libc::opendir(path.as_ptr()) }; - if dirp.is_null() { - return Err(io::Error::last_os_error()); - } + let dirp = unsafe { libc::opendir(path.as_ptr()) }.check_os_err()?; Ok(Directory { dirp, _phantom: PhantomData, @@ -415,10 +400,7 @@ impl TryFrom for Directory<'_> { type Error = io::Error; fn try_from(fd: OwnedFd) -> io::Result { - let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) }; - if dirp.is_null() { - return Err(io::Error::last_os_error()); - } + let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) }.check_os_err()?; Ok(Directory { dirp, _phantom: PhantomData, @@ -449,16 +431,131 @@ impl Drop for Directory<'_> { pub fn rm_rf(path: &CStr) -> io::Result<()> { unsafe { let mut stat: libc::stat = mem::zeroed(); - if libc::lstat(path.as_ptr(), &mut stat) < 0 { - return Err(io::Error::last_os_error()); - } - if (stat.st_mode & libc::S_IFMT as u32) == libc::S_IFDIR as u32 { + libc::lstat(path.as_ptr(), &mut stat).check_os_err()?; + if stat.is_dir() { let mut dir = Directory::open(path)?; dir.remove_all()?; } - if libc::remove(path.as_ptr()) < 0 { - return Err(io::Error::last_os_error()); - } + libc::remove(path.as_ptr()).check_os_err()?; } Ok(()) } + +pub trait StatExt { + fn get_mode(&self) -> u32; + fn get_type(&self) -> u32; + + fn is_dir(&self) -> bool; + fn is_blk(&self) -> bool; +} + +impl StatExt for libc::stat { + fn get_mode(&self) -> u32 { + self.st_mode & libc::S_IFMT as u32 + } + + fn get_type(&self) -> u32 { + self.st_mode & !(libc::S_IFMT as u32) + } + + fn is_dir(&self) -> bool { + self.get_type() == libc::S_IFDIR as u32 + } + + fn is_blk(&self) -> bool { + self.get_type() == libc::S_IFBLK as u32 + } +} + +pub trait FdExt { + fn size(&self) -> io::Result; +} + +const BLKGETSIZE64: u32 = 0x80081272; + +impl FdExt for T { + fn size(&self) -> io::Result { + unsafe fn inner(fd: RawFd) -> io::Result { + extern "C" { + // Don't use the declaration from the libc crate as request should be u32 not i32 + fn ioctl(fd: RawFd, request: u32, ...) -> i32; + } + let mut stat: libc::stat = mem::zeroed(); + libc::fstat(fd, &mut stat).check_os_err()?; + if stat.is_blk() { + let mut sz = 0_u64; + ioctl(fd, BLKGETSIZE64, &mut sz).check_os_err()?; + Ok(sz as usize) + } else { + Ok(stat.st_size as usize) + } + } + + unsafe { inner(self.as_raw_fd()) } + } +} + +pub struct MappedFile(&'static mut [u8]); + +impl MappedFile { + pub fn open(path: &CStr) -> io::Result { + Ok(MappedFile(map_file(path, false)?)) + } + + pub fn open_rw(path: &CStr) -> io::Result { + Ok(MappedFile(map_file(path, true)?)) + } + + pub fn create(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result { + Ok(MappedFile(map_fd(fd, sz, rw)?)) + } +} + +impl AsRef<[u8]> for MappedFile { + fn as_ref(&self) -> &[u8] { + self.0 + } +} + +impl AsMut<[u8]> for MappedFile { + fn as_mut(&mut self) -> &mut [u8] { + self.0 + } +} + +impl Drop for MappedFile { + fn drop(&mut self) { + unsafe { + libc::munmap(self.0.as_mut_ptr().cast(), self.0.len()); + } + } +} + +// We mark the returned slice static because it is valid until explicitly unmapped +pub(crate) fn map_file(path: &CStr, rw: bool) -> io::Result<&'static mut [u8]> { + let flag = if rw { O_RDONLY } else { O_RDWR }; + let fd = open_fd!(path, flag | O_CLOEXEC)?; + map_fd(fd.as_fd(), fd.size()?, rw) +} + +pub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result<&'static mut [u8]> { + let flag = if rw { + libc::MAP_SHARED + } else { + libc::MAP_PRIVATE + }; + unsafe { + let ptr = libc::mmap( + ptr::null_mut(), + sz, + libc::PROT_READ | libc::PROT_WRITE, + flag, + fd.as_raw_fd(), + 0, + ); + if ptr == libc::MAP_FAILED { + return Err(io::Error::last_os_error()); + } + Ok(slice::from_raw_parts_mut(ptr.cast(), sz)) + } +} diff --git a/native/src/base/lib.rs b/native/src/base/lib.rs index 0bb5e08b4..6104b601e 100644 --- a/native/src/base/lib.rs +++ b/native/src/base/lib.rs @@ -38,5 +38,9 @@ pub mod ffi { fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32; #[rust_name = "fd_path_for_cxx"] fn fd_path(fd: i32, buf: &mut [u8]) -> isize; + #[rust_name = "map_file_for_cxx"] + fn map_file(path: &[u8], rw: bool) -> &'static mut [u8]; + #[rust_name = "map_fd_for_cxx"] + fn map_fd(fd: i32, sz: usize, rw: bool) -> &'static mut [u8]; } } diff --git a/native/src/base/misc.rs b/native/src/base/misc.rs index 4e6e67457..e99acd58b 100644 --- a/native/src/base/misc.rs +++ b/native/src/base/misc.rs @@ -1,9 +1,12 @@ use std::cmp::min; -use std::ffi::CStr; -use std::fmt::{Arguments, Debug}; +use std::ffi::{CStr, FromBytesWithNulError, OsStr}; +use std::fmt::{Arguments, Debug, Display, Formatter}; +use std::ops::Deref; +use std::path::Path; use std::str::Utf8Error; -use std::{fmt, slice}; +use std::{fmt, io, slice, str}; +use libc::c_char; use thiserror::Error; pub fn copy_str>(dest: &mut [u8], src: T) -> usize { @@ -96,14 +99,120 @@ macro_rules! raw_cstr { #[derive(Debug, Error)] pub enum StrErr { #[error(transparent)] - Invalid(#[from] Utf8Error), + Utf8Error(#[from] Utf8Error), + #[error(transparent)] + CStrError(#[from] FromBytesWithNulError), #[error("argument is null")] - NullPointer, + NullPointerError, +} + +// The better CStr: UTF-8 validated + null terminated buffer +pub struct Utf8CStr { + inner: [u8], +} + +impl Utf8CStr { + pub fn from_cstr(cstr: &CStr) -> Result<&Utf8CStr, StrErr> { + // Validate the buffer during construction + str::from_utf8(cstr.to_bytes())?; + Ok(unsafe { Self::from_bytes_unchecked(cstr.to_bytes_with_nul()) }) + } + + pub fn from_bytes(buf: &[u8]) -> Result<&Utf8CStr, StrErr> { + Self::from_cstr(CStr::from_bytes_with_nul(buf)?) + } + + pub fn from_string(s: &mut String) -> &Utf8CStr { + if s.capacity() == s.len() { + s.reserve(1); + } + // SAFETY: the string is reserved to have enough capacity to fit in the null byte + // SAFETY: the null byte is explicitly added outside of the string's length + unsafe { + let buf = slice::from_raw_parts_mut(s.as_mut_ptr(), s.len() + 1); + *buf.get_unchecked_mut(s.len()) = b'\0'; + Self::from_bytes_unchecked(buf) + } + } + + pub unsafe fn from_bytes_unchecked(buf: &[u8]) -> &Utf8CStr { + &*(buf as *const [u8] as *const Utf8CStr) + } + + pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a Utf8CStr, StrErr> { + if ptr.is_null() { + return Err(StrErr::NullPointerError); + } + Self::from_cstr(unsafe { CStr::from_ptr(ptr) }) + } + + pub fn as_bytes(&self) -> &[u8] { + // The length of the slice is at least 1 due to null termination check + unsafe { self.inner.get_unchecked(..self.inner.len() - 1) } + } + + pub fn as_bytes_with_nul(&self) -> &[u8] { + &self.inner + } + + pub fn as_ptr(&self) -> *const c_char { + self.inner.as_ptr().cast() + } +} + +impl Deref for Utf8CStr { + type Target = str; + + fn deref(&self) -> &str { + self.as_ref() + } +} + +impl Display for Utf8CStr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(self.deref(), f) + } +} + +impl Debug for Utf8CStr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Debug::fmt(self.deref(), f) + } +} + +impl AsRef for Utf8CStr { + #[inline] + fn as_ref(&self) -> &CStr { + // SAFETY: Already validated as null terminated during construction + unsafe { CStr::from_bytes_with_nul_unchecked(&self.inner) } + } +} + +impl AsRef for Utf8CStr { + #[inline] + fn as_ref(&self) -> &str { + // SAFETY: Already UTF-8 validated during construction + unsafe { str::from_utf8_unchecked(self.as_bytes()) } + } +} + +impl AsRef for Utf8CStr { + #[inline] + fn as_ref(&self) -> &OsStr { + OsStr::new(self) + } +} + +impl AsRef for Utf8CStr { + #[inline] + fn as_ref(&self) -> &Path { + Path::new(self) + } } pub fn ptr_to_str_result<'a, T>(ptr: *const T) -> Result<&'a str, StrErr> { if ptr.is_null() { - Err(StrErr::NullPointer) + Err(StrErr::NullPointerError) } else { unsafe { CStr::from_ptr(ptr.cast()) } .to_str() @@ -167,3 +276,38 @@ pub trait FlatData { } } } + +// Check libc return value and map errors to Result +pub trait LibcReturn: Copy { + fn is_error(&self) -> bool; + fn check_os_err(self) -> io::Result { + if self.is_error() { + return Err(io::Error::last_os_error()); + } + Ok(self) + } +} + +impl LibcReturn for i32 { + fn is_error(&self) -> bool { + *self < 0 + } +} + +impl LibcReturn for isize { + fn is_error(&self) -> bool { + *self < 0 + } +} + +impl LibcReturn for *const T { + fn is_error(&self) -> bool { + self.is_null() + } +} + +impl LibcReturn for *mut T { + fn is_error(&self) -> bool { + self.is_null() + } +} diff --git a/native/src/boot/cpio.rs b/native/src/boot/cpio.rs index d93412b8f..b90215240 100644 --- a/native/src/boot/cpio.rs +++ b/native/src/boot/cpio.rs @@ -4,21 +4,21 @@ use std::fmt::{Display, Formatter}; use std::fs::{metadata, read, DirBuilder, File}; use std::io::Write; use std::mem::size_of; -use std::os::fd::AsRawFd; use std::os::unix::fs::{symlink, DirBuilderExt, FileTypeExt, MetadataExt}; use std::path::Path; use std::process::exit; +use std::slice; use anyhow::{anyhow, Context}; use clap::{Parser, Subcommand}; use size::{Base, Size, Style}; use base::libc::{ - c_char, dev_t, gid_t, major, makedev, minor, mknod, mmap, mode_t, munmap, uid_t, MAP_FAILED, - MAP_PRIVATE, PROT_READ, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_IRGRP, S_IROTH, - S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR, + c_char, dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t, S_IFBLK, S_IFCHR, S_IFDIR, + S_IFLNK, S_IFMT, S_IFREG, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, + S_IXOTH, S_IXUSR, }; -use base::{ptr_to_str_result, ResultExt, WriteExt}; +use base::{MappedFile, ResultExt, StrErr, Utf8CStr, WriteExt}; use crate::ramdisk::MagiskCpio; @@ -152,31 +152,10 @@ impl Cpio { Ok(cpio) } - pub(crate) fn load_from_file(path: &str) -> anyhow::Result { + pub(crate) fn load_from_file(path: &Utf8CStr) -> anyhow::Result { eprintln!("Loading cpio: [{}]", path); - let file = File::open(path)?; - let len = file.metadata()?.len() as usize; - let mmap = unsafe { - mmap( - std::ptr::null_mut(), - len, - PROT_READ, - MAP_PRIVATE, - file.as_raw_fd(), - 0, - ) - }; - if mmap == MAP_FAILED { - return Err(anyhow!("mmap failed")); - } - let data = unsafe { std::slice::from_raw_parts(mmap as *const u8, len) }; - let cpio = Self::load_from_data(data)?; - unsafe { - if munmap(mmap, len) != 0 { - return Err(anyhow!("munmap failed")); - } - } - Ok(cpio) + let data = MappedFile::open(path.as_ref())?; + Self::load_from_data(data.as_ref()) } fn dump(&self, path: &str) -> anyhow::Result<()> { @@ -435,19 +414,18 @@ impl Display for CpioEntry { pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool { fn inner(argc: i32, argv: *const *const c_char) -> anyhow::Result<()> { - let mut cmds = Vec::new(); if argc < 1 { return Err(anyhow!("no arguments")); } - for i in 0..argc { - let arg = unsafe { ptr_to_str_result(*argv.offset(i as isize)) }; - match arg { - Ok(arg) => cmds.push(arg), - Err(e) => Err(e)?, - } - } - let file = cmds[0]; + let cmds: Result, StrErr> = + unsafe { slice::from_raw_parts(argv, argc as usize) } + .iter() + .map(|s| unsafe { Utf8CStr::from_ptr(*s) }) + .collect(); + let cmds = cmds?; + + let file = cmds[0]; let mut cpio = if Path::new(file).exists() { Cpio::load_from_file(file)? } else { @@ -458,8 +436,8 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool { continue; } let cmd = "magiskboot ".to_string() + cmd; - let cli = CpioCli::try_parse_from(cmd.split(' ').filter(|x| !x.is_empty()))?; - match &cli.command { + let mut cli = CpioCli::try_parse_from(cmd.split(' ').filter(|x| !x.is_empty()))?; + match &mut cli.command { CpioCommands::Test {} => exit(cpio.test()), CpioCommands::Restore {} => cpio.restore()?, CpioCommands::Patch {} => cpio.patch(), @@ -470,7 +448,9 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool { exit(1); } } - CpioCommands::Backup { origin } => cpio.backup(origin)?, + CpioCommands::Backup { ref mut origin } => { + cpio.backup(Utf8CStr::from_string(origin))? + } CpioCommands::Rm { path, recursive } => cpio.rm(path, *recursive), CpioCommands::Mv { from, to } => cpio.mv(from, to)?, CpioCommands::Extract { path, out } => { diff --git a/native/src/boot/payload.rs b/native/src/boot/payload.rs index 61aa89d90..9cb2d5249 100644 --- a/native/src/boot/payload.rs +++ b/native/src/boot/payload.rs @@ -196,12 +196,12 @@ pub fn extract_boot_from_payload( let in_path = ptr_to_str_result(in_path)?; let partition = match ptr_to_str_result(partition) { Ok(s) => Some(s), - Err(StrErr::NullPointer) => None, + Err(StrErr::NullPointerError) => None, Err(e) => Err(e)?, }; let out_path = match ptr_to_str_result(out_path) { Ok(s) => Some(s), - Err(StrErr::NullPointer) => None, + Err(StrErr::NullPointerError) => None, Err(e) => Err(e)?, }; do_extract_boot_from_payload(in_path, partition, out_path) diff --git a/native/src/boot/ramdisk.rs b/native/src/boot/ramdisk.rs index 64109f3e4..2ee849e91 100644 --- a/native/src/boot/ramdisk.rs +++ b/native/src/boot/ramdisk.rs @@ -4,6 +4,7 @@ use std::env; use std::str::from_utf8; use base::libc::{S_IFDIR, S_IFMT, S_IFREG}; +use base::Utf8CStr; use crate::cpio::{Cpio, CpioEntry}; use crate::ffi::{patch_encryption, patch_verity}; @@ -12,7 +13,7 @@ pub trait MagiskCpio { fn patch(&mut self); fn test(&self) -> i32; fn restore(&mut self) -> anyhow::Result<()>; - fn backup(&mut self, origin: &str) -> anyhow::Result<()>; + fn backup(&mut self, origin: &Utf8CStr) -> anyhow::Result<()>; } const MAGISK_PATCHED: i32 = 1 << 0; @@ -119,7 +120,7 @@ impl MagiskCpio for Cpio { Ok(()) } - fn backup(&mut self, origin: &str) -> anyhow::Result<()> { + fn backup(&mut self, origin: &Utf8CStr) -> anyhow::Result<()> { let mut backups = HashMap::::new(); let mut rm_list = String::new(); backups.insert(