From 26e653e12022f6dde97b23ead0a087f7cecb666a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Smolarek?= <34063647+Razz4780@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:05:26 +0200 Subject: [PATCH] `not-found` file filter (#1825) * File filter and config updated * Schema udpated * Changelog entry * macos fix * Removed env * Removed aws paths from default remote-readonly filter * Test fix * Tests fix * Obsolete env reads removed --- changelog.d/1694.fixed.md | 1 + mirrord-schema.json | 14 +- mirrord/config/src/feature/fs.rs | 2 + mirrord/config/src/feature/fs/advanced.rs | 30 +- mirrord/config/src/feature/fs/mode.rs | 4 + mirrord/layer/src/error.rs | 6 + mirrord/layer/src/file/filter.rs | 403 ++++++++++++++-------- mirrord/layer/src/lib.rs | 1 + 8 files changed, 316 insertions(+), 145 deletions(-) create mode 100644 changelog.d/1694.fixed.md diff --git a/changelog.d/1694.fixed.md b/changelog.d/1694.fixed.md new file mode 100644 index 00000000000..da6ef241f54 --- /dev/null +++ b/changelog.d/1694.fixed.md @@ -0,0 +1 @@ +Added an extra `not-found` file filter to improve experience when using cloud services under mirrord. \ No newline at end of file diff --git a/mirrord-schema.json b/mirrord-schema.json index b45e4892138..7f42e0ae63e 100644 --- a/mirrord-schema.json +++ b/mirrord-schema.json @@ -156,7 +156,7 @@ "additionalProperties": false, "definitions": { "AdvancedFsUserConfig": { - "description": "Allows the user to specify the default behavior for file operations:\n\n1. `\"read\"` - Read from the remote file system (default) 2. `\"write\"` - Read/Write from the remote file system. 3. `\"local\"` - Read from the local file system. 5. `\"disable\"` - Disable file operations.\n\nBesides the default behavior, the user can specify behavior for specific regex patterns. Case insensitive.\n\n1. `\"read_write\"` - List of patterns that should be read/write remotely. 2. `\"read_only\"` - List of patterns that should be read only remotely. 3. `\"local\"` - List of patterns that should be read locally.\n\nThe logic for choosing the behavior is as follows:\n\n1. Check if one of the patterns match the file path, do the corresponding action. There's no specified order if two lists match the same path, we will use the first one (and we do not guarantee what is first).\n\n**Warning**: Specifying the same path in two lists is unsupported and can lead to undefined behaviour.\n\n2. Check our \"special list\" - we have an internal at compile time list for different behavior based on patterns to provide better UX.\n\n3. If none of the above match, use the default behavior (mode).\n\nFor more information, check the file operations [technical reference](https://mirrord.dev/docs/reference/fileops/).\n\n```json { \"feature\": { \"fs\": { \"mode\": \"write\", \"read_write\": \".+\\.json\" , \"read_only\": [ \".+\\.yaml\", \".+important-file\\.txt\" ], \"local\": [ \".+\\.js\", \".+\\.mjs\" ] } } } ```", + "description": "Allows the user to specify the default behavior for file operations:\n\n1. `\"read\"` - Read from the remote file system (default) 2. `\"write\"` - Read/Write from the remote file system. 3. `\"local\"` - Read from the local file system. 5. `\"disable\"` - Disable file operations.\n\nBesides the default behavior, the user can specify behavior for specific regex patterns. Case insensitive.\n\n1. `\"read_write\"` - List of patterns that should be read/write remotely. 2. `\"read_only\"` - List of patterns that should be read only remotely. 3. `\"local\"` - List of patterns that should be read locally. 4. `\"not_found\"` - List of patters that should never be read nor written. These files should be treated as non-existent.\n\nThe logic for choosing the behavior is as follows:\n\n1. Check if one of the patterns match the file path, do the corresponding action. There's no specified order if two lists match the same path, we will use the first one (and we do not guarantee what is first).\n\n**Warning**: Specifying the same path in two lists is unsupported and can lead to undefined behaviour.\n\n2. Check our \"special list\" - we have an internal at compile time list for different behavior based on patterns to provide better UX.\n\n3. If none of the above match, use the default behavior (mode).\n\nFor more information, check the file operations [technical reference](https://mirrord.dev/docs/reference/fileops/).\n\n```json { \"feature\": { \"fs\": { \"mode\": \"write\", \"read_write\": \".+\\.json\" , \"read_only\": [ \".+\\.yaml\", \".+important-file\\.txt\" ], \"local\": [ \".+\\.js\", \".+\\.mjs\" ], \"not_found\": [ \"\\.config/gcloud\" ] } } } ```", "type": "object", "properties": { "local": { @@ -182,6 +182,18 @@ } ] }, + "not_found": { + "title": "feature.fs.not_found {#feature-fs-not_found}", + "description": "Specify file path patterns that if matched will be treated as non-existent.", + "anyOf": [ + { + "$ref": "#/definitions/VecOrSingle_for_String" + }, + { + "type": "null" + } + ] + }, "read_only": { "title": "feature.fs.read_only {#feature-fs-read_only}", "description": "Specify file path patterns that if matched will be read from the remote. if file matching the pattern is opened for writing or read/write it will be opened locally.", diff --git a/mirrord/config/src/feature/fs.rs b/mirrord/config/src/feature/fs.rs index 2e00cd17efe..11fc1a3c3a9 100644 --- a/mirrord/config/src/feature/fs.rs +++ b/mirrord/config/src/feature/fs.rs @@ -87,6 +87,7 @@ impl MirrordConfig for FsUserConfig { local: FromEnv::new("MIRRORD_FILE_LOCAL_PATTERN") .source_value() .transpose()?, + not_found: None, }, FsUserConfig::Advanced(advanced) => advanced.generate_config()?, }; @@ -113,6 +114,7 @@ impl MirrordToggleableConfig for FsUserConfig { read_write, read_only, local, + not_found: None, }) } } diff --git a/mirrord/config/src/feature/fs/advanced.rs b/mirrord/config/src/feature/fs/advanced.rs index 163ef88050d..be31ac9ef84 100644 --- a/mirrord/config/src/feature/fs/advanced.rs +++ b/mirrord/config/src/feature/fs/advanced.rs @@ -25,6 +25,8 @@ use crate::{ /// 1. `"read_write"` - List of patterns that should be read/write remotely. /// 2. `"read_only"` - List of patterns that should be read only remotely. /// 3. `"local"` - List of patterns that should be read locally. +/// 4. `"not_found"` - List of patters that should never be read nor written. These files should be +/// treated as non-existent. /// /// The logic for choosing the behavior is as follows: /// @@ -50,7 +52,8 @@ use crate::{ /// "mode": "write", /// "read_write": ".+\.json" , /// "read_only": [ ".+\.yaml", ".+important-file\.txt" ], -/// "local": [ ".+\.js", ".+\.mjs" ] +/// "local": [ ".+\.js", ".+\.mjs" ], +/// "not_found": [ "\.config/gcloud" ] /// } /// } /// } @@ -83,6 +86,11 @@ pub struct FsConfig { /// Specify file path patterns that if matched will be opened locally. #[config(env = "MIRRORD_FILE_LOCAL_PATTERN")] pub local: Option>, + + /// ### feature.fs.not_found {#feature-fs-not_found} + /// + /// Specify file path patterns that if matched will be treated as non-existent. + pub not_found: Option>, } impl MirrordToggleableConfig for AdvancedFsUserConfig { @@ -103,6 +111,7 @@ impl MirrordToggleableConfig for AdvancedFsUserConfig { read_write, read_only, local, + not_found: None, }) } } @@ -138,18 +147,31 @@ impl CollectAnalytics for &FsConfig { analytics.add("mode", self.mode); analytics.add( "local_paths", - self.local.as_ref().map(|v| v.len()).unwrap_or_default(), + self.local + .as_ref() + .map(VecOrSingle::len) + .unwrap_or_default(), ); analytics.add( "read_write_paths", self.read_write .as_ref() - .map(|v| v.len()) + .map(VecOrSingle::len) .unwrap_or_default(), ); analytics.add( "read_only_paths", - self.read_only.as_ref().map(|v| v.len()).unwrap_or_default(), + self.read_only + .as_ref() + .map(VecOrSingle::len) + .unwrap_or_default(), + ); + analytics.add( + "not_found_paths", + self.not_found + .as_ref() + .map(VecOrSingle::len) + .unwrap_or_default(), ); } } diff --git a/mirrord/config/src/feature/fs/mode.rs b/mirrord/config/src/feature/fs/mode.rs index 2f0d579bd68..e9f1d3a99d6 100644 --- a/mirrord/config/src/feature/fs/mode.rs +++ b/mirrord/config/src/feature/fs/mode.rs @@ -46,6 +46,10 @@ pub enum FsModeConfig { } impl FsModeConfig { + pub fn is_local(self) -> bool { + matches!(self, FsModeConfig::Local) + } + pub fn is_read(self) -> bool { matches!(self, FsModeConfig::Read | FsModeConfig::LocalWithOverrides) } diff --git a/mirrord/layer/src/error.rs b/mirrord/layer/src/error.rs index 5f37deba28c..6567107309a 100644 --- a/mirrord/layer/src/error.rs +++ b/mirrord/layer/src/error.rs @@ -96,6 +96,11 @@ pub(crate) enum HookError { #[error("mirrord-layer: Socket address `{0}` is already bound!")] AddressAlreadyBound(SocketAddr), + + /// When the user's application tries to access a file filtered out by the `not-found` file + /// filter. + #[error("mirrord-layer: Ignored file")] + FileNotFound, } /// Errors internal to mirrord-layer. @@ -307,6 +312,7 @@ impl From for i64 { HookError::UnsupportedSocketType => libc::EAFNOSUPPORT, HookError::BadPointer => libc::EFAULT, HookError::AddressAlreadyBound(_) => libc::EADDRINUSE, + HookError::FileNotFound => libc::ENOENT, }; set_errno(errno::Errno(libc_error)); diff --git a/mirrord/layer/src/file/filter.rs b/mirrord/layer/src/file/filter.rs index f6f02cb7392..ab86c352c0e 100644 --- a/mirrord/layer/src/file/filter.rs +++ b/mirrord/layer/src/file/filter.rs @@ -15,18 +15,10 @@ use mirrord_config::{ use regex::{RegexSet, RegexSetBuilder}; use tracing::warn; -use crate::detour::{Bypass, Detour}; - -/// Shortcut for checking an optional regex set. -/// Usage is `some_regex_match(Option, text)` -macro_rules! some_regex_match { - ($regex:expr, $text:expr) => { - $regex - .as_ref() - .map(|__regex| __regex.is_match($text)) - .unwrap_or_default() - }; -} +use crate::{ + detour::{Bypass, Detour}, + error::HookError, +}; /// List of files that mirrord should use locally, as they probably exist only in the local user /// machine, or are system configuration files (that could break the process if we used the remote @@ -110,11 +102,6 @@ fn generate_local_set() -> RegexSet { /// List of files that mirrord should use remotely read only fn generate_remote_ro_set() -> RegexSet { let patterns = [ - // AWS cli cache - // "file not exist" for identity caches (AWS) - r".aws/cli/cache/.+\.json$", - r".aws/credentials$", - r".aws/config$", // for dns resolving r"^/etc/resolv.conf$", r"^/etc/hosts$", @@ -123,7 +110,16 @@ fn generate_remote_ro_set() -> RegexSet { RegexSetBuilder::new(patterns) .case_insensitive(true) .build() - .expect("Building local path regex set failed") + .expect("Building remote readonly path regex set failed") +} + +fn generate_not_found_set() -> RegexSet { + let patterns = [r"\.aws", r"\.config/gcloud", r"\.kube", r"\.azure"]; + + RegexSetBuilder::new(patterns) + .case_insensitive(true) + .build() + .expect("Building not found path regex set failed") } /// Global filter used by file operations to bypass (use local) or continue (use remote). @@ -131,29 +127,23 @@ pub(crate) static FILE_FILTER: OnceLock = OnceLock::new(); #[derive(Debug)] pub(crate) struct FileFilter { - read_only: Option, - read_write: Option, - local: Option, + read_only: RegexSet, + read_write: RegexSet, + local: RegexSet, + not_found: RegexSet, default_local: RegexSet, default_remote_ro: RegexSet, + default_not_found: RegexSet, mode: FsModeConfig, } -/// Builds case insensitive regexes from a list of patterns. -/// Returns `None` if the list is empty. -/// Regex error if fails. -fn build_regex_or_none(patterns: Vec) -> Result, regex::Error> { - if patterns.is_empty() { - Ok(None) - } else { - RegexSetBuilder::new(patterns) +impl FileFilter { + fn make_regex_set(patterns: Option>) -> Result { + RegexSetBuilder::new(patterns.map(VecOrSingle::to_vec).unwrap_or_default()) .case_insensitive(true) .build() - .map(Some) } -} -impl FileFilter { /// Initializes a `FileFilter` based on the user configuration. /// /// The filter first checks if the user specified any include/exclude regexes. (This will be @@ -167,25 +157,29 @@ impl FileFilter { read_only, local, mode, + not_found, } = fs_config; let read_write = - build_regex_or_none(read_write.map(VecOrSingle::to_vec).unwrap_or_default()) - .expect("Building read-write regex set failed"); - let read_only = build_regex_or_none(read_only.map(VecOrSingle::to_vec).unwrap_or_default()) - .expect("Building read-only regex set failed"); - let local = build_regex_or_none(local.map(VecOrSingle::to_vec).unwrap_or_default()) - .expect("Building local path regex set failed"); + Self::make_regex_set(read_write).expect("building read-write regex set failed"); + let read_only = + Self::make_regex_set(read_only).expect("building read-only regex set failed"); + let local = Self::make_regex_set(local).expect("building local path regex set failed"); + let not_found = + Self::make_regex_set(not_found).expect("building not-found regex set failed"); let default_local = generate_local_set(); let default_remote_ro = generate_remote_ro_set(); + let default_not_found = generate_not_found_set(); Self { read_only, read_write, local, + not_found, default_local, default_remote_ro, + default_not_found, mode, } } @@ -198,31 +192,25 @@ impl FileFilter { where F: FnOnce() -> Bypass, { - if matches!(&self.mode, FsModeConfig::Local) { - return Detour::Bypass(op()); - } - - if some_regex_match!(self.read_write, text) { - Detour::Success(()) - } else if some_regex_match!(self.read_only, text) { - if !write { - Detour::Success(()) - } else { - Detour::Bypass(op()) - } - } else if some_regex_match!(self.local, text) { - Detour::Bypass(op()) - } else if self.default_remote_ro.is_match(text) && !write { - Detour::Success(()) - } else if self.default_local.is_match(text) { - Detour::Bypass(op()) - } else { - match self.mode { - FsModeConfig::Write => Detour::Success(()), - FsModeConfig::Read if !write => Detour::Success(()), - FsModeConfig::Read if write => Detour::Bypass(Bypass::ReadOnly(text.into())), - _ => Detour::Bypass(op()), + match self.mode { + FsModeConfig::Local => Detour::Bypass(op()), + _ if self.not_found.is_match(text) => Detour::Error(HookError::FileNotFound), + _ if self.read_write.is_match(text) => Detour::Success(()), + _ if self.read_only.is_match(text) => { + if write { + Detour::Bypass(op()) + } else { + Detour::Success(()) + } } + _ if self.default_not_found.is_match(text) => Detour::Error(HookError::FileNotFound), + _ if self.local.is_match(text) => Detour::Bypass(op()), + _ if self.default_remote_ro.is_match(text) && !write => Detour::Success(()), + _ if self.default_local.is_match(text) => Detour::Bypass(op()), + FsModeConfig::LocalWithOverrides => Detour::Bypass(op()), + FsModeConfig::Write => Detour::Success(()), + FsModeConfig::Read if write => Detour::Bypass(Bypass::ReadOnly(text.into())), + FsModeConfig::Read => Detour::Success(()), } } } @@ -241,92 +229,221 @@ mod tests { use super::*; use crate::detour::{Bypass, Detour}; - /// Implementation of helper methods for testing [`Detour`]. + /// Helper type for testing [`FileFilter`] results. + #[derive(PartialEq, Eq, Debug)] + enum DetourKind { + Bypass, + Error, + Success, + } + impl Detour { - /// Convenience function to convert [`Detour::Bypass`] to `bool`. - fn is_bypass(&self) -> bool { - matches!(self, Detour::Bypass(_)) + fn kind(&self) -> DetourKind { + match self { + Self::Bypass(..) => DetourKind::Bypass, + Self::Error(..) => DetourKind::Error, + Self::Success(..) => DetourKind::Success, + } } } #[rstest] #[trace] - #[case(FsModeConfig::Write, "/a/test.a", false, false)] - #[case(FsModeConfig::Write, "/pain/read_write/test.a", false, false)] - #[case(FsModeConfig::Write, "/pain/read_only/test.a", false, false)] - #[case(FsModeConfig::Write, "/pain/write.a", false, false)] - #[case(FsModeConfig::Write, "/pain/local/test.a", false, true)] - #[case(FsModeConfig::Write, "/opt/test.a", false, true)] - #[case(FsModeConfig::Write, "/a/test.a", true, false)] - #[case(FsModeConfig::Write, "/pain/read_write/test.a", true, false)] - #[case(FsModeConfig::Write, "/pain/read_only/test.a", true, true)] - #[case(FsModeConfig::Write, "/pain/write.a", true, false)] - #[case(FsModeConfig::Write, "/pain/local/test.a", true, true)] - #[case(FsModeConfig::Write, "/opt/test.a", true, true)] - #[case(FsModeConfig::Read, "/a/test.a", false, false)] - #[case(FsModeConfig::Read, "/pain/read_write/test.a", false, false)] - #[case(FsModeConfig::Read, "/pain/read_only/test.a", false, false)] - #[case(FsModeConfig::Read, "/pain/write.a", false, false)] - #[case(FsModeConfig::Read, "/pain/local/test.a", false, true)] - #[case(FsModeConfig::Read, "/opt/test.a", false, true)] - #[case(FsModeConfig::Read, "/a/test.a", true, true)] - #[case(FsModeConfig::Read, "/pain/read_write/test.a", true, false)] - #[case(FsModeConfig::Read, "/pain/read_only/test.a", true, true)] - #[case(FsModeConfig::Read, "/pain/write.a", true, true)] - #[case(FsModeConfig::Read, "/pain/local/test.a", true, true)] - #[case(FsModeConfig::Read, "/opt/test.a", true, true)] - #[case(FsModeConfig::LocalWithOverrides, "/a/test.a", false, true)] + #[case(FsModeConfig::Write, "/a/test.a", false, DetourKind::Success)] + #[case( + FsModeConfig::Write, + "/pain/read_write/test.a", + false, + DetourKind::Success + )] + #[case( + FsModeConfig::Write, + "/pain/read_only/test.a", + false, + DetourKind::Success + )] + #[case(FsModeConfig::Write, "/pain/write.a", false, DetourKind::Success)] + #[case(FsModeConfig::Write, "/pain/local/test.a", false, DetourKind::Bypass)] + #[case(FsModeConfig::Write, "/opt/test.a", false, DetourKind::Bypass)] + #[case(FsModeConfig::Write, "/a/test.a", true, DetourKind::Success)] + #[case( + FsModeConfig::Write, + "/pain/read_write/test.a", + true, + DetourKind::Success + )] + #[case( + FsModeConfig::Write, + "/pain/read_only/test.a", + true, + DetourKind::Bypass + )] + #[case(FsModeConfig::Write, "/pain/write.a", true, DetourKind::Success)] + #[case(FsModeConfig::Write, "/pain/local/test.a", true, DetourKind::Bypass)] + #[case(FsModeConfig::Write, "/opt/test.a", true, DetourKind::Bypass)] + #[case(FsModeConfig::Read, "/a/test.a", false, DetourKind::Success)] + #[case( + FsModeConfig::Read, + "/pain/read_write/test.a", + false, + DetourKind::Success + )] + #[case( + FsModeConfig::Read, + "/pain/read_only/test.a", + false, + DetourKind::Success + )] + #[case(FsModeConfig::Read, "/pain/write.a", false, DetourKind::Success)] + #[case(FsModeConfig::Read, "/pain/local/test.a", false, DetourKind::Bypass)] + #[case(FsModeConfig::Read, "/opt/test.a", false, DetourKind::Bypass)] + #[case(FsModeConfig::Read, "/a/test.a", true, DetourKind::Bypass)] + #[case( + FsModeConfig::Read, + "/pain/read_write/test.a", + true, + DetourKind::Success + )] + #[case(FsModeConfig::Read, "/pain/read_only/test.a", true, DetourKind::Bypass)] + #[case(FsModeConfig::Read, "/pain/write.a", true, DetourKind::Bypass)] + #[case(FsModeConfig::Read, "/pain/local/test.a", true, DetourKind::Bypass)] + #[case(FsModeConfig::Read, "/opt/test.a", true, DetourKind::Bypass)] + #[case( + FsModeConfig::LocalWithOverrides, + "/a/test.a", + false, + DetourKind::Bypass + )] #[case( FsModeConfig::LocalWithOverrides, "/pain/read_write/test.a", false, - false + DetourKind::Success )] #[case( FsModeConfig::LocalWithOverrides, "/pain/read_only/test.a", false, - false + DetourKind::Success + )] + #[case( + FsModeConfig::LocalWithOverrides, + "/pain/write.a", + false, + DetourKind::Bypass + )] + #[case( + FsModeConfig::LocalWithOverrides, + "/pain/local/test.a", + false, + DetourKind::Bypass + )] + #[case( + FsModeConfig::LocalWithOverrides, + "/opt/test.a", + false, + DetourKind::Bypass + )] + #[case( + FsModeConfig::LocalWithOverrides, + "/a/test.a", + true, + DetourKind::Bypass )] - #[case(FsModeConfig::LocalWithOverrides, "/pain/write.a", false, true)] - #[case(FsModeConfig::LocalWithOverrides, "/pain/local/test.a", false, true)] - #[case(FsModeConfig::LocalWithOverrides, "/opt/test.a", false, true)] - #[case(FsModeConfig::LocalWithOverrides, "/a/test.a", true, true)] #[case( FsModeConfig::LocalWithOverrides, "/pain/read_write/test.a", true, - false + DetourKind::Success + )] + #[case( + FsModeConfig::LocalWithOverrides, + "/pain/read_only/test.a", + true, + DetourKind::Bypass + )] + #[case( + FsModeConfig::LocalWithOverrides, + "/pain/write.a", + true, + DetourKind::Bypass + )] + #[case( + FsModeConfig::LocalWithOverrides, + "/pain/local/test.a", + true, + DetourKind::Bypass )] - #[case(FsModeConfig::LocalWithOverrides, "/pain/read_only/test.a", true, true)] - #[case(FsModeConfig::LocalWithOverrides, "/pain/write.a", true, true)] - #[case(FsModeConfig::LocalWithOverrides, "/pain/local/test.a", true, true)] - #[case(FsModeConfig::LocalWithOverrides, "/opt/test.a", true, true)] - #[case(FsModeConfig::Read, "/usr/a/.aws/cli/cache/121.json", true, true)] - #[case(FsModeConfig::Write, "/usr/a/.aws/cli/cache/121.json", true, true)] #[case( FsModeConfig::LocalWithOverrides, - "/usr/a/.aws/cli/cache/121.json", + "/opt/test.a", + true, + DetourKind::Bypass + )] + #[case(FsModeConfig::Read, "/etc/resolv.conf", true, DetourKind::Bypass)] + #[case(FsModeConfig::Write, "/etc/resolv.conf", true, DetourKind::Bypass)] + #[case( + FsModeConfig::LocalWithOverrides, + "/etc/resolv.conf", + true, + DetourKind::Bypass + )] + #[case(FsModeConfig::Local, "/a/test.a", false, DetourKind::Bypass)] + #[case( + FsModeConfig::Local, + "/pain/read_write/test.a", + false, + DetourKind::Bypass + )] + #[case( + FsModeConfig::Local, + "/pain/read_only/test.a", + false, + DetourKind::Bypass + )] + #[case(FsModeConfig::Local, "/pain/write.a", false, DetourKind::Bypass)] + #[case(FsModeConfig::Local, "/pain/local/test.a", false, DetourKind::Bypass)] + #[case(FsModeConfig::Local, "/opt/test.a", false, DetourKind::Bypass)] + #[case(FsModeConfig::Local, "/a/test.a", true, DetourKind::Bypass)] + #[case( + FsModeConfig::Local, + "/pain/read_write/test.a", + true, + DetourKind::Bypass + )] + #[case( + FsModeConfig::Local, + "/pain/read_only/test.a", true, - true + DetourKind::Bypass + )] + #[case(FsModeConfig::Local, "/pain/write.a", true, DetourKind::Bypass)] + #[case(FsModeConfig::Local, "/pain/local/test.a", true, DetourKind::Bypass)] + #[case(FsModeConfig::Local, "/opt/test.a", true, DetourKind::Bypass)] + #[case( + FsModeConfig::Local, + "/pain/not_found/test.a", + false, + DetourKind::Bypass + )] + #[case( + FsModeConfig::LocalWithOverrides, + "/pain/not_found/test.a", + false, + DetourKind::Error + )] + #[case(FsModeConfig::Read, "/pain/not_found/test.a", false, DetourKind::Error)] + #[case( + FsModeConfig::Write, + "/pain/not_found/test.a", + false, + DetourKind::Error )] - #[case(FsModeConfig::Local, "/a/test.a", false, true)] - #[case(FsModeConfig::Local, "/pain/read_write/test.a", false, true)] - #[case(FsModeConfig::Local, "/pain/read_only/test.a", false, true)] - #[case(FsModeConfig::Local, "/pain/write.a", false, true)] - #[case(FsModeConfig::Local, "/pain/local/test.a", false, true)] - #[case(FsModeConfig::Local, "/opt/test.a", false, true)] - #[case(FsModeConfig::Local, "/a/test.a", true, true)] - #[case(FsModeConfig::Local, "/pain/read_write/test.a", true, true)] - #[case(FsModeConfig::Local, "/pain/read_only/test.a", true, true)] - #[case(FsModeConfig::Local, "/pain/write.a", true, true)] - #[case(FsModeConfig::Local, "/pain/local/test.a", true, true)] - #[case(FsModeConfig::Local, "/opt/test.a", true, true)] fn include_complex_configuration( #[case] mode: FsModeConfig, #[case] path: &str, #[case] write: bool, - #[case] bypass: bool, + #[case] expected: DetourKind, ) { let read_write = Some(VecOrSingle::Multiple(vec![ r"/pain/read_write.*\.a".to_string() @@ -335,45 +452,45 @@ mod tests { r"/pain/read_only.*\.a".to_string() ])); let local = Some(VecOrSingle::Multiple(vec![r"/pain/local.*\.a".to_string()])); + let not_found = Some(VecOrSingle::Single(r"/pain/not_found.*\.a".to_string())); let fs_config = FsConfig { read_write, read_only, local, + not_found, mode, }; let file_filter = FileFilter::new(fs_config); - assert_eq!( - file_filter - .continue_or_bypass_with(path, write, || Bypass::IgnoredFile("".into())) - .is_bypass(), - bypass - ); + let res = + file_filter.continue_or_bypass_with(path, write, || Bypass::IgnoredFile("".into())); + println!("filter result: {res:?}"); + assert_eq!(res.kind(), expected); } #[rstest] - #[case(FsModeConfig::Read, "/usr/a/.aws/cli/cache/121.json", true, true)] - #[case(FsModeConfig::Write, "/usr/a/.aws/cli/cache/121.json", true, true)] + #[case(FsModeConfig::Read, "/etc/resolv.conf", true, DetourKind::Bypass)] + #[case(FsModeConfig::Write, "/etc/resolv.conf", true, DetourKind::Bypass)] #[case( FsModeConfig::LocalWithOverrides, - "/usr/a/.aws/cli/cache/121.json", + "/etc/resolv.conf", true, - true + DetourKind::Bypass )] - #[case(FsModeConfig::Read, "/usr/a/.aws/cli/cache/121.json", false, false)] - #[case(FsModeConfig::Write, "/usr/a/.aws/cli/cache/121.json", false, false)] + #[case(FsModeConfig::Read, "/etc/resolv.conf", false, DetourKind::Success)] + #[case(FsModeConfig::Write, "/etc/resolv.conf", false, DetourKind::Success)] #[case( FsModeConfig::LocalWithOverrides, - "/usr/a/.aws/cli/cache/1241.json", + "/etc/resolv.conf", false, - false + DetourKind::Success )] fn remote_read_only_set( #[case] mode: FsModeConfig, #[case] path: &str, #[case] write: bool, - #[case] bypass: bool, + #[case] expected: DetourKind, ) { let fs_config = FsConfig { mode, @@ -382,11 +499,17 @@ mod tests { let file_filter = FileFilter::new(fs_config); - assert_eq!( - file_filter - .continue_or_bypass_with(path, write, || Bypass::IgnoredFile("".into())) - .is_bypass(), - bypass - ); + let res = + file_filter.continue_or_bypass_with(path, write, || Bypass::IgnoredFile("".into())); + println!("filter result: {res:?}"); + + assert_eq!(res.kind(), expected); + } + + /// Sanity test for empty [`RegexSet`] behaviour. + #[test] + fn empty_regex_set() { + let set = FileFilter::make_regex_set(None).unwrap(); + assert!(!set.is_match("/path/to/some/file")); } } diff --git a/mirrord/layer/src/lib.rs b/mirrord/layer/src/lib.rs index d2580d57baa..565dca248b3 100644 --- a/mirrord/layer/src/lib.rs +++ b/mirrord/layer/src/lib.rs @@ -545,6 +545,7 @@ fn sip_only_layer_start(patch_binaries: Vec) { read_write: None, read_only: None, local: None, + not_found: None, })) .expect("FILE_FILTER set failed"); unsafe { file::hooks::enable_file_hooks(&mut hook_manager) };