summaryrefslogtreecommitdiffstats
path: root/src/modules/utils
diff options
context:
space:
mode:
authorAlexey Chernyshov <eiden127@gmail.com>2020-07-14 00:55:42 +0300
committerGitHub <noreply@github.com>2020-07-13 16:55:42 -0500
commit57c39437bc50992dfe11b104229db854cb912a18 (patch)
tree516b372bf34aab77a53dc9a7c40bcf71ee03d1f0 /src/modules/utils
parent0db640396b8684c2bed5ad5b4052dc2e70fca7a6 (diff)
feat(directory): Show lock symbol if current directory is read only (#1298)
Add feature to display icon if current directory is read-only.
Diffstat (limited to 'src/modules/utils')
-rw-r--r--src/modules/utils/directory_nix.rs61
-rw-r--r--src/modules/utils/directory_win.rs117
-rw-r--r--src/modules/utils/mod.rs6
3 files changed, 184 insertions, 0 deletions
diff --git a/src/modules/utils/directory_nix.rs b/src/modules/utils/directory_nix.rs
new file mode 100644
index 000000000..87f94b7eb
--- /dev/null
+++ b/src/modules/utils/directory_nix.rs
@@ -0,0 +1,61 @@
+use nix::sys::stat::Mode;
+use nix::unistd::{Gid, Uid};
+use std::fs;
+use std::os::unix::fs::MetadataExt;
+use std::os::unix::fs::PermissionsExt;
+
+/// Checks if the current user can write to the `folder_path`.
+///
+/// It extracts Unix access rights from the directory and checks whether
+/// 1) the current user is the owner of the directory and whether it has the write access
+/// 2) the current user's primary group is the directory group owner whether if it has write access
+/// 2a) (not implemented on macOS) one of the supplementary groups of the current user is the
+/// directory group owner and whether it has write access
+/// 3) 'others' part of the access mask has the write access
+pub fn is_write_allowed(folder_path: &str) -> Result<bool, &'static str> {
+ let meta = fs::metadata(folder_path).map_err(|_| "Unable to stat() directory")?;
+ let perms = meta.permissions().mode();
+
+ let euid = Uid::effective();
+ if euid.is_root() {
+ return Ok(true);
+ }
+ if meta.uid() == euid.as_raw() {
+ Ok(perms & Mode::S_IWUSR.bits() as u32 != 0)
+ } else if (meta.gid() == Gid::effective().as_raw())
+ || (get_supplementary_groups().contains(&meta.gid()))
+ {
+ Ok(perms & Mode::S_IWGRP.bits() as u32 != 0)
+ } else {
+ Ok(perms & Mode::S_IWOTH.bits() as u32 != 0)
+ }
+}
+
+#[cfg(all(unix, not(target_os = "macos")))]
+fn get_supplementary_groups() -> Vec<u32> {
+ match nix::unistd::getgroups() {
+ Err(_) => Vec::new(),
+ Ok(v) => v.into_iter().map(|i| i.as_raw()).collect(),
+ }
+}
+
+#[cfg(all(unix, target_os = "macos"))]
+fn get_supplementary_groups() -> Vec<u32> {
+ // at the moment nix crate does not provide it for macOS
+ Vec::new()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ #[ignore]
+ fn read_only_test() {
+ assert_eq!(is_write_allowed("/etc"), Ok(false));
+ assert_eq!(
+ is_write_allowed("/i_dont_exist"),
+ Err("Unable to stat() directory")
+ );
+ }
+}
diff --git a/src/modules/utils/directory_win.rs b/src/modules/utils/directory_win.rs
new file mode 100644
index 000000000..6574be555
--- /dev/null
+++ b/src/modules/utils/directory_win.rs
@@ -0,0 +1,117 @@
+extern crate winapi;
+
+use std::ffi::OsStr;
+use std::iter;
+use std::mem;
+use std::os::windows::ffi::OsStrExt;
+use winapi::ctypes::c_void;
+use winapi::shared::minwindef::{BOOL, DWORD};
+use winapi::um::handleapi;
+use winapi::um::processthreadsapi;
+use winapi::um::securitybaseapi;
+use winapi::um::winnt::{
+ SecurityImpersonation, DACL_SECURITY_INFORMATION, FILE_ALL_ACCESS, FILE_GENERIC_EXECUTE,
+ FILE_GENERIC_READ, FILE_GENERIC_WRITE, GENERIC_MAPPING, GROUP_SECURITY_INFORMATION, HANDLE,
+ OWNER_SECURITY_INFORMATION, PRIVILEGE_SET, PSECURITY_DESCRIPTOR, STANDARD_RIGHTS_READ,
+ TOKEN_DUPLICATE, TOKEN_IMPERSONATE, TOKEN_QUERY,
+};
+
+/// Checks if the current user has write access right to the `folder_path`
+///
+/// First, the function extracts DACL from the given directory and then calls `AccessCheck` against
+/// the current process access token and directory's security descriptor.
+pub fn is_write_allowed(folder_path: &str) -> std::result::Result<bool, &'static str> {
+ let folder_name: Vec<u16> = OsStr::new(folder_path)
+ .encode_wide()
+ .chain(iter::once(0))
+ .collect();
+ let mut length: DWORD = 0;
+
+ let rc = unsafe {
+ securitybaseapi::GetFileSecurityW(
+ folder_name.as_ptr(),
+ OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
+ std::ptr::null_mut(),
+ 0,
+ &mut length,
+ )
+ };
+ if rc != 0 {
+ return Err(
+ "GetFileSecurityW returned non-zero when asked for the security descriptor size",
+ );
+ }
+
+ let mut buf: Vec<u8> = Vec::with_capacity(length as usize);
+
+ let rc = unsafe {
+ securitybaseapi::GetFileSecurityW(
+ folder_name.as_ptr(),
+ OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
+ buf.as_mut_ptr() as *mut c_void,
+ length,
+ &mut length,
+ )
+ };
+
+ if rc != 1 {
+ return Err("GetFileSecurityW failed to retrieve the security descriptor");
+ }
+
+ let mut token: HANDLE = 0 as HANDLE;
+ let rc = unsafe {
+ processthreadsapi::OpenProcessToken(
+ processthreadsapi::GetCurrentProcess(),
+ TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ,
+ &mut token,
+ )
+ };
+ if rc != 1 {
+ return Err("OpenProcessToken failed to retrieve current process' security token");
+ }
+
+ let mut impersonated_token: HANDLE = 0 as HANDLE;
+ let rc = unsafe {
+ securitybaseapi::DuplicateToken(token, SecurityImpersonation, &mut impersonated_token)
+ };
+ if rc != 1 {
+ unsafe { handleapi::CloseHandle(token) };
+ return Err("DuplicateToken failed");
+ }
+
+ let mut mapping: GENERIC_MAPPING = GENERIC_MAPPING {
+ GenericRead: FILE_GENERIC_READ,
+ GenericWrite: FILE_GENERIC_WRITE,
+ GenericExecute: FILE_GENERIC_EXECUTE,
+ GenericAll: FILE_ALL_ACCESS,
+ };
+
+ let mut priviledges: PRIVILEGE_SET = PRIVILEGE_SET::default();
+ let mut priv_size = mem::size_of::<PRIVILEGE_SET>() as DWORD;
+ let mut granted_access: DWORD = 0;
+ let mut access_rights: DWORD = FILE_GENERIC_WRITE;
+ let mut result: BOOL = 0 as BOOL;
+ unsafe { securitybaseapi::MapGenericMask(&mut access_rights, &mut mapping) };
+ let rc = unsafe {
+ securitybaseapi::AccessCheck(
+ buf.as_mut_ptr() as PSECURITY_DESCRIPTOR,
+ impersonated_token,
+ access_rights,
+ &mut mapping,
+ &mut priviledges,
+ &mut priv_size,
+ &mut granted_access,
+ &mut result,
+ )
+ };
+ unsafe {
+ handleapi::CloseHandle(impersonated_token);
+ handleapi::CloseHandle(token);
+ }
+
+ if rc != 1 {
+ return Err("AccessCheck failed");
+ }
+
+ Ok(result != 0)
+}
diff --git a/src/modules/utils/mod.rs b/src/modules/utils/mod.rs
index 345ce7f40..d164de4d7 100644
--- a/src/modules/utils/mod.rs
+++ b/src/modules/utils/mod.rs
@@ -1,5 +1,11 @@
pub mod directory;
pub mod java_version_parser;
+#[cfg(target_os = "windows")]
+pub mod directory_win;
+
+#[cfg(not(target_os = "windows"))]
+pub mod directory_nix;
+
#[cfg(test)]
pub mod test;