solana_runtime/snapshot_utils/
archive_format.rs

1use {
2    std::{fmt, str::FromStr},
3    strum::Display,
4};
5
6// SUPPORTED_ARCHIVE_COMPRESSION lists the compression types that can be
7// specified on the command line. "zstd" and "lz4" are valid whereas "gzip",
8// "bz2", "tar" and "none" have been deprecated. Thus, all newly created
9// snapshots will either use "zstd" or "lz4". By keeping the deprecated types
10// in the ArchiveFormat enum, pre-existing snapshot archives with the
11// deprecated compression types can still be read.
12pub const SUPPORTED_ARCHIVE_COMPRESSION: &[&str] = &["zstd", "lz4"];
13pub const DEFAULT_ARCHIVE_COMPRESSION: &str = "zstd";
14
15pub const TAR_BZIP2_EXTENSION: &str = "tar.bz2";
16pub const TAR_GZIP_EXTENSION: &str = "tar.gz";
17pub const TAR_ZSTD_EXTENSION: &str = "tar.zst";
18pub const TAR_LZ4_EXTENSION: &str = "tar.lz4";
19pub const TAR_EXTENSION: &str = "tar";
20
21/// The different archive formats used for snapshots
22#[derive(Copy, Clone, Debug, Eq, PartialEq, Display)]
23pub enum ArchiveFormat {
24    TarBzip2,
25    TarGzip,
26    TarZstd { config: ZstdConfig },
27    TarLz4,
28    Tar,
29}
30
31impl ArchiveFormat {
32    /// Get the file extension for the ArchiveFormat
33    pub fn extension(&self) -> &str {
34        match self {
35            ArchiveFormat::TarBzip2 => TAR_BZIP2_EXTENSION,
36            ArchiveFormat::TarGzip => TAR_GZIP_EXTENSION,
37            ArchiveFormat::TarZstd { .. } => TAR_ZSTD_EXTENSION,
38            ArchiveFormat::TarLz4 => TAR_LZ4_EXTENSION,
39            ArchiveFormat::Tar => TAR_EXTENSION,
40        }
41    }
42
43    pub fn from_cli_arg(archive_format_str: &str) -> Option<ArchiveFormat> {
44        match archive_format_str {
45            "zstd" => Some(ArchiveFormat::TarZstd {
46                config: ZstdConfig::default(),
47            }),
48            "lz4" => Some(ArchiveFormat::TarLz4),
49            _ => None,
50        }
51    }
52}
53
54// Change this to `impl<S: AsRef<str>> TryFrom<S> for ArchiveFormat [...]`
55// once this Rust bug is fixed: https://github.com/rust-lang/rust/issues/50133
56impl TryFrom<&str> for ArchiveFormat {
57    type Error = ParseError;
58
59    fn try_from(extension: &str) -> Result<Self, Self::Error> {
60        match extension {
61            TAR_BZIP2_EXTENSION => Ok(ArchiveFormat::TarBzip2),
62            TAR_GZIP_EXTENSION => Ok(ArchiveFormat::TarGzip),
63            TAR_ZSTD_EXTENSION => Ok(ArchiveFormat::TarZstd {
64                config: ZstdConfig::default(),
65            }),
66            TAR_LZ4_EXTENSION => Ok(ArchiveFormat::TarLz4),
67            TAR_EXTENSION => Ok(ArchiveFormat::Tar),
68            _ => Err(ParseError::InvalidExtension(extension.to_string())),
69        }
70    }
71}
72
73impl FromStr for ArchiveFormat {
74    type Err = ParseError;
75
76    fn from_str(extension: &str) -> Result<Self, Self::Err> {
77        Self::try_from(extension)
78    }
79}
80
81#[derive(Debug, Clone, Eq, PartialEq)]
82pub enum ParseError {
83    InvalidExtension(String),
84}
85
86impl fmt::Display for ParseError {
87    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88        match self {
89            ParseError::InvalidExtension(extension) => {
90                write!(f, "Invalid archive extension: {extension}")
91            }
92        }
93    }
94}
95
96/// Configuration when using zstd as the snapshot archive format
97#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
98pub struct ZstdConfig {
99    /// The compression level to use when archiving with zstd
100    pub compression_level: i32,
101}
102
103#[cfg(test)]
104mod tests {
105    use {super::*, std::iter::zip};
106    const INVALID_EXTENSION: &str = "zip";
107
108    #[test]
109    fn test_extension() {
110        assert_eq!(ArchiveFormat::TarBzip2.extension(), TAR_BZIP2_EXTENSION);
111        assert_eq!(ArchiveFormat::TarGzip.extension(), TAR_GZIP_EXTENSION);
112        assert_eq!(
113            ArchiveFormat::TarZstd {
114                config: ZstdConfig::default(),
115            }
116            .extension(),
117            TAR_ZSTD_EXTENSION
118        );
119        assert_eq!(ArchiveFormat::TarLz4.extension(), TAR_LZ4_EXTENSION);
120        assert_eq!(ArchiveFormat::Tar.extension(), TAR_EXTENSION);
121    }
122
123    #[test]
124    fn test_try_from() {
125        assert_eq!(
126            ArchiveFormat::try_from(TAR_BZIP2_EXTENSION),
127            Ok(ArchiveFormat::TarBzip2)
128        );
129        assert_eq!(
130            ArchiveFormat::try_from(TAR_GZIP_EXTENSION),
131            Ok(ArchiveFormat::TarGzip)
132        );
133        assert_eq!(
134            ArchiveFormat::try_from(TAR_ZSTD_EXTENSION),
135            Ok(ArchiveFormat::TarZstd {
136                config: ZstdConfig::default(),
137            })
138        );
139        assert_eq!(
140            ArchiveFormat::try_from(TAR_LZ4_EXTENSION),
141            Ok(ArchiveFormat::TarLz4)
142        );
143        assert_eq!(
144            ArchiveFormat::try_from(TAR_EXTENSION),
145            Ok(ArchiveFormat::Tar)
146        );
147        assert_eq!(
148            ArchiveFormat::try_from(INVALID_EXTENSION),
149            Err(ParseError::InvalidExtension(INVALID_EXTENSION.to_string()))
150        );
151    }
152
153    #[test]
154    fn test_from_str() {
155        assert_eq!(
156            ArchiveFormat::from_str(TAR_BZIP2_EXTENSION),
157            Ok(ArchiveFormat::TarBzip2)
158        );
159        assert_eq!(
160            ArchiveFormat::from_str(TAR_GZIP_EXTENSION),
161            Ok(ArchiveFormat::TarGzip)
162        );
163        assert_eq!(
164            ArchiveFormat::from_str(TAR_ZSTD_EXTENSION),
165            Ok(ArchiveFormat::TarZstd {
166                config: ZstdConfig::default(),
167            })
168        );
169        assert_eq!(
170            ArchiveFormat::from_str(TAR_LZ4_EXTENSION),
171            Ok(ArchiveFormat::TarLz4)
172        );
173        assert_eq!(
174            ArchiveFormat::from_str(TAR_EXTENSION),
175            Ok(ArchiveFormat::Tar)
176        );
177        assert_eq!(
178            ArchiveFormat::from_str(INVALID_EXTENSION),
179            Err(ParseError::InvalidExtension(INVALID_EXTENSION.to_string()))
180        );
181    }
182
183    #[test]
184    fn test_from_cli_arg() {
185        let golden = [
186            Some(ArchiveFormat::TarZstd {
187                config: ZstdConfig::default(),
188            }),
189            Some(ArchiveFormat::TarLz4),
190        ];
191
192        for (arg, expected) in zip(SUPPORTED_ARCHIVE_COMPRESSION.iter(), golden.into_iter()) {
193            assert_eq!(ArchiveFormat::from_cli_arg(arg), expected);
194        }
195
196        assert_eq!(ArchiveFormat::from_cli_arg("bad"), None);
197    }
198}