file.rs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  1. //! Files, and methods and fields to access their metadata.
  2. use std::io;
  3. #[cfg(unix)]
  4. use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
  5. #[cfg(windows)]
  6. use std::os::windows::fs::MetadataExt;
  7. use std::path::{Path, PathBuf};
  8. use std::sync::OnceLock;
  9. use chrono::prelude::*;
  10. use log::*;
  11. use crate::fs::dir::Dir;
  12. use crate::fs::feature::xattr;
  13. use crate::fs::feature::xattr::{Attribute, FileAttributes};
  14. use crate::fs::fields as f;
  15. use super::mounts::all_mounts;
  16. use super::mounts::MountedFs;
  17. /// A **File** is a wrapper around one of Rust’s `PathBuf` values, along with
  18. /// associated data about the file.
  19. ///
  20. /// Each file is definitely going to have its filename displayed at least
  21. /// once, have its file extension extracted at least once, and have its metadata
  22. /// information queried at least once, so it makes sense to do all this at the
  23. /// start and hold on to all the information.
  24. pub struct File<'dir> {
  25. /// The filename portion of this file’s path, including the extension.
  26. ///
  27. /// This is used to compare against certain filenames (such as checking if
  28. /// it’s “Makefile” or something) and to highlight only the filename in
  29. /// colour when displaying the path.
  30. pub name: String,
  31. /// The file’s name’s extension, if present, extracted from the name.
  32. ///
  33. /// This is queried many times over, so it’s worth caching it.
  34. pub ext: Option<String>,
  35. /// The path that begat this file.
  36. ///
  37. /// Even though the file’s name is extracted, the path needs to be kept
  38. /// around, as certain operations involve looking up the file’s absolute
  39. /// location (such as searching for compiled files) or using its original
  40. /// path (following a symlink).
  41. pub path: PathBuf,
  42. /// A cached `metadata` (`stat`) call for this file.
  43. ///
  44. /// This too is queried multiple times, and is *not* cached by the OS, as
  45. /// it could easily change between invocations — but exa is so short-lived
  46. /// it’s better to just cache it.
  47. pub metadata: std::fs::Metadata,
  48. /// A reference to the directory that contains this file, if any.
  49. ///
  50. /// Filenames that get passed in on the command-line directly will have no
  51. /// parent directory reference — although they technically have one on the
  52. /// filesystem, we’ll never need to look at it, so it’ll be `None`.
  53. /// However, *directories* that get passed in will produce files that
  54. /// contain a reference to it, which is used in certain operations (such
  55. /// as looking up compiled files).
  56. pub parent_dir: Option<&'dir Dir>,
  57. /// Whether this is one of the two `--all all` directories, `.` and `..`.
  58. ///
  59. /// Unlike all other entries, these are not returned as part of the
  60. /// directory’s children, and are in fact added specifically by exa; this
  61. /// means that they should be skipped when recursing.
  62. pub is_all_all: bool,
  63. /// Whether to dereference symbolic links when querying for information.
  64. ///
  65. /// For instance, when querying the size of a symbolic link, if
  66. /// dereferencing is enabled, the size of the target will be displayed
  67. /// instead.
  68. pub deref_links: bool,
  69. /// The extended attributes of this file.
  70. extended_attributes: OnceLock<Vec<Attribute>>,
  71. /// The absolute value of this path, used to look up mount points.
  72. absolute_path: OnceLock<Option<PathBuf>>,
  73. }
  74. impl<'dir> File<'dir> {
  75. pub fn from_args<PD, FN>(
  76. path: PathBuf,
  77. parent_dir: PD,
  78. filename: FN,
  79. deref_links: bool,
  80. ) -> io::Result<File<'dir>>
  81. where
  82. PD: Into<Option<&'dir Dir>>,
  83. FN: Into<Option<String>>,
  84. {
  85. let parent_dir = parent_dir.into();
  86. let name = filename.into().unwrap_or_else(|| File::filename(&path));
  87. let ext = File::ext(&path);
  88. debug!("Statting file {:?}", &path);
  89. let metadata = std::fs::symlink_metadata(&path)?;
  90. let is_all_all = false;
  91. let extended_attributes = OnceLock::new();
  92. let absolute_path = OnceLock::new();
  93. Ok(File {
  94. name,
  95. ext,
  96. path,
  97. metadata,
  98. parent_dir,
  99. is_all_all,
  100. deref_links,
  101. extended_attributes,
  102. absolute_path,
  103. })
  104. }
  105. pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
  106. let path = parent_dir.path.clone();
  107. let ext = File::ext(&path);
  108. debug!("Statting file {:?}", &path);
  109. let metadata = std::fs::symlink_metadata(&path)?;
  110. let is_all_all = true;
  111. let parent_dir = Some(parent_dir);
  112. let extended_attributes = OnceLock::new();
  113. let absolute_path = OnceLock::new();
  114. Ok(File {
  115. path,
  116. parent_dir,
  117. metadata,
  118. ext,
  119. name: ".".into(),
  120. is_all_all,
  121. deref_links: false,
  122. extended_attributes,
  123. absolute_path,
  124. })
  125. }
  126. pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
  127. let ext = File::ext(&path);
  128. debug!("Statting file {:?}", &path);
  129. let metadata = std::fs::symlink_metadata(&path)?;
  130. let is_all_all = true;
  131. let parent_dir = Some(parent_dir);
  132. let extended_attributes = OnceLock::new();
  133. let absolute_path = OnceLock::new();
  134. Ok(File {
  135. path,
  136. parent_dir,
  137. metadata,
  138. ext,
  139. name: "..".into(),
  140. is_all_all,
  141. deref_links: false,
  142. extended_attributes,
  143. absolute_path,
  144. })
  145. }
  146. /// A file’s name is derived from its string. This needs to handle directories
  147. /// such as `/` or `..`, which have no `file_name` component. So instead, just
  148. /// use the last component as the name.
  149. pub fn filename(path: &Path) -> String {
  150. if let Some(back) = path.components().next_back() {
  151. back.as_os_str().to_string_lossy().to_string()
  152. } else {
  153. // use the path as fallback
  154. error!("Path {:?} has no last component", path);
  155. path.display().to_string()
  156. }
  157. }
  158. /// Extract an extension from a file path, if one is present, in lowercase.
  159. ///
  160. /// The extension is the series of characters after the last dot. This
  161. /// deliberately counts dotfiles, so the “.git” folder has the extension “git”.
  162. ///
  163. /// ASCII lowercasing is used because these extensions are only compared
  164. /// against a pre-compiled list of extensions which are known to only exist
  165. /// within ASCII, so it’s alright.
  166. fn ext(path: &Path) -> Option<String> {
  167. let name = path.file_name().map(|f| f.to_string_lossy().to_string())?;
  168. name.rfind('.').map(|p| name[p + 1..].to_ascii_lowercase())
  169. }
  170. /// Read the extended attributes of a file path.
  171. fn gather_extended_attributes(path: &Path) -> Vec<Attribute> {
  172. if xattr::ENABLED {
  173. match path.symlink_attributes() {
  174. Ok(xattrs) => xattrs,
  175. Err(e) => {
  176. error!(
  177. "Error looking up extended attributes for {}: {}",
  178. path.display(),
  179. e
  180. );
  181. Vec::new()
  182. }
  183. }
  184. } else {
  185. Vec::new()
  186. }
  187. }
  188. /// Get the extended attributes of a file path on demand.
  189. pub fn extended_attributes(&self) -> &Vec<Attribute> {
  190. self.extended_attributes
  191. .get_or_init(|| File::gather_extended_attributes(&self.path))
  192. }
  193. /// Whether this file is a directory on the filesystem.
  194. pub fn is_directory(&self) -> bool {
  195. self.metadata.is_dir()
  196. }
  197. /// Whether this file is a directory, or a symlink pointing to a directory.
  198. pub fn points_to_directory(&self) -> bool {
  199. if self.is_directory() {
  200. return true;
  201. }
  202. if self.is_link() {
  203. let target = self.link_target();
  204. if let FileTarget::Ok(target) = target {
  205. return target.points_to_directory();
  206. }
  207. }
  208. false
  209. }
  210. /// If this file is a directory on the filesystem, then clone its
  211. /// `PathBuf` for use in one of our own `Dir` values, and read a list of
  212. /// its contents.
  213. ///
  214. /// Returns an IO error upon failure, but this shouldn’t be used to check
  215. /// if a `File` is a directory or not! For that, just use `is_directory()`.
  216. pub fn to_dir(&self) -> io::Result<Dir> {
  217. trace!("to_dir: reading dir");
  218. Dir::read_dir(self.path.clone())
  219. }
  220. /// Whether this file is a regular file on the filesystem — that is, not a
  221. /// directory, a link, or anything else treated specially.
  222. pub fn is_file(&self) -> bool {
  223. self.metadata.is_file()
  224. }
  225. /// Whether this file is both a regular file *and* executable for the
  226. /// current user. An executable file has a different purpose from an
  227. /// executable directory, so they should be highlighted differently.
  228. #[cfg(unix)]
  229. pub fn is_executable_file(&self) -> bool {
  230. let bit = modes::USER_EXECUTE;
  231. self.is_file() && (self.metadata.permissions().mode() & bit) == bit
  232. }
  233. /// Whether this file is a symlink on the filesystem.
  234. pub fn is_link(&self) -> bool {
  235. self.metadata.file_type().is_symlink()
  236. }
  237. /// Whether this file is a named pipe on the filesystem.
  238. #[cfg(unix)]
  239. pub fn is_pipe(&self) -> bool {
  240. self.metadata.file_type().is_fifo()
  241. }
  242. /// Whether this file is a char device on the filesystem.
  243. #[cfg(unix)]
  244. pub fn is_char_device(&self) -> bool {
  245. self.metadata.file_type().is_char_device()
  246. }
  247. /// Whether this file is a block device on the filesystem.
  248. #[cfg(unix)]
  249. pub fn is_block_device(&self) -> bool {
  250. self.metadata.file_type().is_block_device()
  251. }
  252. /// Whether this file is a socket on the filesystem.
  253. #[cfg(unix)]
  254. pub fn is_socket(&self) -> bool {
  255. self.metadata.file_type().is_socket()
  256. }
  257. /// Determine the full path resolving all symbolic links on demand.
  258. pub fn absolute_path(&self) -> Option<&PathBuf> {
  259. self.absolute_path
  260. .get_or_init(|| std::fs::canonicalize(&self.path).ok())
  261. .as_ref()
  262. }
  263. /// Whether this file is a mount point
  264. pub fn is_mount_point(&self) -> bool {
  265. cfg!(any(target_os = "linux", target_os = "macos"))
  266. && self.is_directory()
  267. && self
  268. .absolute_path()
  269. .is_some_and(|p| all_mounts().contains_key(p))
  270. }
  271. /// The filesystem device and type for a mount point
  272. pub fn mount_point_info(&self) -> Option<&MountedFs> {
  273. if cfg!(any(target_os = "linux", target_os = "macos")) {
  274. return self.absolute_path().and_then(|p| all_mounts().get(p));
  275. }
  276. None
  277. }
  278. /// Re-prefixes the path pointed to by this file, if it’s a symlink, to
  279. /// make it an absolute path that can be accessed from whichever
  280. /// directory exa is being run from.
  281. fn reorient_target_path(&self, path: &Path) -> PathBuf {
  282. if path.is_absolute() {
  283. path.to_path_buf()
  284. } else if let Some(dir) = self.parent_dir {
  285. dir.join(path)
  286. } else if let Some(parent) = self.path.parent() {
  287. parent.join(path)
  288. } else {
  289. self.path.join(path)
  290. }
  291. }
  292. /// Again assuming this file is a symlink, follows that link and returns
  293. /// the result of following it.
  294. ///
  295. /// For a working symlink that the user is allowed to follow,
  296. /// this will be the `File` object at the other end, which can then have
  297. /// its name, colour, and other details read.
  298. ///
  299. /// For a broken symlink, returns where the file *would* be, if it
  300. /// existed. If this file cannot be read at all, returns the error that
  301. /// we got when we tried to read it.
  302. pub fn link_target(&self) -> FileTarget<'dir> {
  303. // We need to be careful to treat the path actually pointed to by
  304. // this file — which could be absolute or relative — to the path
  305. // we actually look up and turn into a `File` — which needs to be
  306. // absolute to be accessible from any directory.
  307. debug!("Reading link {:?}", &self.path);
  308. let path = match std::fs::read_link(&self.path) {
  309. Ok(p) => p,
  310. Err(e) => return FileTarget::Err(e),
  311. };
  312. let absolute_path = self.reorient_target_path(&path);
  313. // Use plain `metadata` instead of `symlink_metadata` - we *want* to
  314. // follow links.
  315. match std::fs::metadata(&absolute_path) {
  316. Ok(metadata) => {
  317. let ext = File::ext(&path);
  318. let name = File::filename(&path);
  319. let extended_attributes = OnceLock::new();
  320. let absolute_path_cell = OnceLock::from(Some(absolute_path));
  321. let file = File {
  322. parent_dir: None,
  323. path,
  324. ext,
  325. metadata,
  326. name,
  327. is_all_all: false,
  328. deref_links: self.deref_links,
  329. extended_attributes,
  330. absolute_path: absolute_path_cell,
  331. };
  332. FileTarget::Ok(Box::new(file))
  333. }
  334. Err(e) => {
  335. error!("Error following link {:?}: {:#?}", &path, e);
  336. FileTarget::Broken(path)
  337. }
  338. }
  339. }
  340. /// Assuming this file is a symlink, follows that link and any further
  341. /// links recursively, returning the result from following the trail.
  342. ///
  343. /// For a working symlink that the user is allowed to follow,
  344. /// this will be the `File` object at the other end, which can then have
  345. /// its name, colour, and other details read.
  346. ///
  347. /// For a broken symlink, returns where the file *would* be, if it
  348. /// existed. If this file cannot be read at all, returns the error that
  349. /// we got when we tried to read it.
  350. pub fn link_target_recurse(&self) -> FileTarget<'dir> {
  351. let target = self.link_target();
  352. if let FileTarget::Ok(f) = target {
  353. if f.is_link() {
  354. return f.link_target_recurse();
  355. }
  356. return FileTarget::Ok(f);
  357. }
  358. target
  359. }
  360. /// This file’s number of hard links.
  361. ///
  362. /// It also reports whether this is both a regular file, and a file with
  363. /// multiple links. This is important, because a file with multiple links
  364. /// is uncommon, while you come across directories and other types
  365. /// with multiple links much more often. Thus, it should get highlighted
  366. /// more attentively.
  367. #[cfg(unix)]
  368. pub fn links(&self) -> f::Links {
  369. let count = self.metadata.nlink();
  370. f::Links {
  371. count,
  372. multiple: self.is_file() && count > 1,
  373. }
  374. }
  375. /// This file’s inode.
  376. #[cfg(unix)]
  377. pub fn inode(&self) -> f::Inode {
  378. f::Inode(self.metadata.ino())
  379. }
  380. /// This actual size the file takes up on disk, in bytes.
  381. #[cfg(unix)]
  382. pub fn blocksize(&self) -> f::Blocksize {
  383. if self.is_file() || self.is_link() {
  384. // Note that metadata.blocks returns the number of blocks
  385. // for 512 byte blocks according to the POSIX standard
  386. // even though the physical block size may be different.
  387. f::Blocksize::Some(self.metadata.blocks() * 512)
  388. } else {
  389. f::Blocksize::None
  390. }
  391. }
  392. /// The ID of the user that own this file. If dereferencing links, the links
  393. /// may be broken, in which case `None` will be returned.
  394. #[cfg(unix)]
  395. pub fn user(&self) -> Option<f::User> {
  396. if self.is_link() && self.deref_links {
  397. match self.link_target_recurse() {
  398. FileTarget::Ok(f) => return f.user(),
  399. _ => return None,
  400. }
  401. }
  402. Some(f::User(self.metadata.uid()))
  403. }
  404. /// The ID of the group that owns this file.
  405. #[cfg(unix)]
  406. pub fn group(&self) -> Option<f::Group> {
  407. if self.is_link() && self.deref_links {
  408. match self.link_target_recurse() {
  409. FileTarget::Ok(f) => return f.group(),
  410. _ => return None,
  411. }
  412. }
  413. Some(f::Group(self.metadata.gid()))
  414. }
  415. /// This file’s size, if it’s a regular file.
  416. ///
  417. /// For directories, no size is given. Although they do have a size on
  418. /// some filesystems, I’ve never looked at one of those numbers and gained
  419. /// any information from it. So it’s going to be hidden instead.
  420. ///
  421. /// Block and character devices return their device IDs, because they
  422. /// usually just have a file size of zero.
  423. ///
  424. /// Links will return the size of their target (recursively through other
  425. /// links) if dereferencing is enabled, otherwise the size of the link
  426. /// itself.
  427. #[cfg(unix)]
  428. pub fn size(&self) -> f::Size {
  429. if self.is_link() {
  430. let target = self.link_target();
  431. if let FileTarget::Ok(target) = target {
  432. return target.size();
  433. }
  434. }
  435. if self.is_directory() {
  436. f::Size::None
  437. } else if self.is_char_device() || self.is_block_device() {
  438. let device_id = self.metadata.rdev();
  439. // MacOS and Linux have different arguments and return types for the
  440. // functions major and minor. On Linux the try_into().unwrap() and
  441. // the "as u32" cast are not needed. We turn off the warning to
  442. // allow it to compile cleanly on Linux.
  443. #[allow(trivial_numeric_casts)]
  444. #[allow(clippy::unnecessary_cast)]
  445. #[allow(clippy::useless_conversion)]
  446. f::Size::DeviceIDs(f::DeviceIDs {
  447. // SAFETY: Calling libc function to decompose the device_id
  448. major: unsafe { libc::major(device_id.try_into().unwrap()) } as u32,
  449. minor: unsafe { libc::minor(device_id.try_into().unwrap()) } as u32,
  450. })
  451. } else if self.is_link() && self.deref_links {
  452. match self.link_target() {
  453. FileTarget::Ok(f) => f.size(),
  454. _ => f::Size::None,
  455. }
  456. } else {
  457. f::Size::Some(self.metadata.len())
  458. }
  459. }
  460. /// Recursive folder size
  461. #[cfg(unix)]
  462. pub fn recursive_size(&self, toplevel: bool) -> f::Size {
  463. use crate::fs::RECURSIVE_SIZE_HASHMAP;
  464. if self.is_directory() {
  465. let mut recursive_size: u64 = 0;
  466. let Ok(dir) = Dir::read_dir(self.path.clone()) else {
  467. return f::Size::None;
  468. };
  469. let files = dir.files(super::DotFilter::Dotfiles, None, false, false);
  470. for file in files.flatten() {
  471. if file.is_file() {
  472. recursive_size += file.metadata.size();
  473. } else {
  474. recursive_size += match file.recursive_size(false) {
  475. f::Size::Some(s) => s,
  476. _ => file.metadata.size(),
  477. };
  478. }
  479. }
  480. if toplevel {
  481. RECURSIVE_SIZE_HASHMAP
  482. .lock()
  483. .unwrap()
  484. .insert((self.metadata.dev(), self.metadata.ino()), recursive_size);
  485. }
  486. f::Size::Some(recursive_size)
  487. } else if self.is_char_device() || self.is_block_device() {
  488. let device_id = self.metadata.rdev();
  489. // MacOS and Linux have different arguments and return types for the
  490. // functions major and minor. On Linux the try_into().unwrap() and
  491. // the "as u32" cast are not needed. We turn off the warning to
  492. // allow it to compile cleanly on Linux.
  493. #[allow(trivial_numeric_casts)]
  494. #[allow(clippy::unnecessary_cast)]
  495. #[allow(clippy::useless_conversion)]
  496. f::Size::DeviceIDs(f::DeviceIDs {
  497. // SAFETY: Calling libc function to decompose the device_id
  498. major: unsafe { libc::major(device_id.try_into().unwrap()) } as u32,
  499. minor: unsafe { libc::minor(device_id.try_into().unwrap()) } as u32,
  500. })
  501. } else if self.is_link() && self.deref_links {
  502. match self.link_target() {
  503. FileTarget::Ok(f) => f::Size::Some(f.metadata.size()),
  504. _ => f::Size::None,
  505. }
  506. } else {
  507. f::Size::Some(self.metadata.len())
  508. }
  509. }
  510. /// Returns the size of the file or indicates no size if it's a directory.
  511. ///
  512. /// For Windows platforms, the size of directories is not computed and will
  513. /// return `Size::None`.
  514. #[cfg(windows)]
  515. pub fn size(&self) -> f::Size {
  516. if self.is_directory() {
  517. f::Size::None
  518. } else {
  519. f::Size::Some(self.metadata.len())
  520. }
  521. }
  522. #[cfg(windows)]
  523. pub fn recursive_size(&self, _toplevel: bool) -> f::Size {
  524. if self.is_directory() {
  525. f::Size::None
  526. } else {
  527. f::Size::Some(self.metadata.len())
  528. }
  529. }
  530. /// Determines if the directory is empty or not.
  531. ///
  532. /// For Unix platforms, this function first checks the link count to quickly
  533. /// determine non-empty directories. On most UNIX filesystems the link count
  534. /// is two plus the number of subdirectories. If the link count is less than
  535. /// or equal to 2, it then checks the directory contents to determine if
  536. /// it's truly empty. The naive approach used here checks the contents
  537. /// directly, as certain filesystems make it difficult to infer emptiness
  538. /// based on directory size alone.
  539. #[cfg(unix)]
  540. pub fn is_empty_dir(&self) -> bool {
  541. if self.is_directory() {
  542. if self.metadata.nlink() > 2 {
  543. // Directories will have a link count of two if they do not have any subdirectories.
  544. // The '.' entry is a link to itself and the '..' is a link to the parent directory.
  545. // A subdirectory will have a link to its parent directory increasing the link count
  546. // above two. This will avoid the expensive read_dir call below when a directory
  547. // has subdirectories.
  548. false
  549. } else {
  550. self.is_empty_directory()
  551. }
  552. } else {
  553. false
  554. }
  555. }
  556. /// Determines if the directory is empty or not.
  557. ///
  558. /// For Windows platforms, this function checks the directory contents directly
  559. /// to determine if it's empty. Since certain filesystems on Windows make it
  560. /// challenging to infer emptiness based on directory size, this approach is used.
  561. #[cfg(windows)]
  562. pub fn is_empty_dir(&self) -> bool {
  563. if self.is_directory() {
  564. self.is_empty_directory()
  565. } else {
  566. false
  567. }
  568. }
  569. /// Checks the contents of the directory to determine if it's empty.
  570. ///
  571. /// This function avoids counting '.' and '..' when determining if the directory is
  572. /// empty. If any other entries are found, it returns `false`.
  573. ///
  574. /// The naive approach, as one would think that this info may have been cached.
  575. /// but as mentioned in the size function comment above, different filesystems
  576. /// make it difficult to get any info about a dir by it's size, so this may be it.
  577. fn is_empty_directory(&self) -> bool {
  578. trace!("is_empty_directory: reading dir");
  579. match Dir::read_dir(self.path.clone()) {
  580. // . & .. are skipped, if the returned iterator has .next(), it's not empty
  581. Ok(has_files) => has_files
  582. .files(super::DotFilter::Dotfiles, None, false, false)
  583. .next()
  584. .is_none(),
  585. Err(_) => false,
  586. }
  587. }
  588. /// This file’s last modified timestamp, if available on this platform.
  589. pub fn modified_time(&self) -> Option<NaiveDateTime> {
  590. if self.is_link() && self.deref_links {
  591. return match self.link_target_recurse() {
  592. FileTarget::Ok(f) => f.modified_time(),
  593. _ => None,
  594. };
  595. }
  596. self.metadata
  597. .modified()
  598. .map(|st| DateTime::<Utc>::from(st).naive_utc())
  599. .ok()
  600. }
  601. /// This file’s last changed timestamp, if available on this platform.
  602. #[cfg(unix)]
  603. pub fn changed_time(&self) -> Option<NaiveDateTime> {
  604. if self.is_link() && self.deref_links {
  605. return match self.link_target_recurse() {
  606. FileTarget::Ok(f) => f.changed_time(),
  607. _ => None,
  608. };
  609. }
  610. NaiveDateTime::from_timestamp_opt(self.metadata.ctime(), self.metadata.ctime_nsec() as u32)
  611. }
  612. #[cfg(windows)]
  613. pub fn changed_time(&self) -> Option<NaiveDateTime> {
  614. self.modified_time()
  615. }
  616. /// This file’s last accessed timestamp, if available on this platform.
  617. pub fn accessed_time(&self) -> Option<NaiveDateTime> {
  618. if self.is_link() && self.deref_links {
  619. return match self.link_target_recurse() {
  620. FileTarget::Ok(f) => f.accessed_time(),
  621. _ => None,
  622. };
  623. }
  624. self.metadata
  625. .accessed()
  626. .map(|st| DateTime::<Utc>::from(st).naive_utc())
  627. .ok()
  628. }
  629. /// This file’s created timestamp, if available on this platform.
  630. pub fn created_time(&self) -> Option<NaiveDateTime> {
  631. if self.is_link() && self.deref_links {
  632. return match self.link_target_recurse() {
  633. FileTarget::Ok(f) => f.created_time(),
  634. _ => None,
  635. };
  636. }
  637. self.metadata
  638. .created()
  639. .map(|st| DateTime::<Utc>::from(st).naive_utc())
  640. .ok()
  641. }
  642. /// This file’s ‘type’.
  643. ///
  644. /// This is used a the leftmost character of the permissions column.
  645. /// The file type can usually be guessed from the colour of the file, but
  646. /// ls puts this character there.
  647. #[cfg(unix)]
  648. pub fn type_char(&self) -> f::Type {
  649. if self.is_file() {
  650. f::Type::File
  651. } else if self.is_directory() {
  652. f::Type::Directory
  653. } else if self.is_pipe() {
  654. f::Type::Pipe
  655. } else if self.is_link() {
  656. f::Type::Link
  657. } else if self.is_char_device() {
  658. f::Type::CharDevice
  659. } else if self.is_block_device() {
  660. f::Type::BlockDevice
  661. } else if self.is_socket() {
  662. f::Type::Socket
  663. } else {
  664. f::Type::Special
  665. }
  666. }
  667. #[cfg(windows)]
  668. pub fn type_char(&self) -> f::Type {
  669. if self.is_file() {
  670. f::Type::File
  671. } else if self.is_directory() {
  672. f::Type::Directory
  673. } else {
  674. f::Type::Special
  675. }
  676. }
  677. /// This file’s permissions, with flags for each bit.
  678. #[cfg(unix)]
  679. pub fn permissions(&self) -> Option<f::Permissions> {
  680. if self.is_link() && self.deref_links {
  681. // If the chain of links is broken, we instead fall through and
  682. // return the permissions of the original link, as would have been
  683. // done if we were not dereferencing.
  684. match self.link_target_recurse() {
  685. FileTarget::Ok(f) => return f.permissions(),
  686. _ => return None,
  687. }
  688. }
  689. let bits = self.metadata.mode();
  690. let has_bit = |bit| bits & bit == bit;
  691. Some(f::Permissions {
  692. user_read: has_bit(modes::USER_READ),
  693. user_write: has_bit(modes::USER_WRITE),
  694. user_execute: has_bit(modes::USER_EXECUTE),
  695. group_read: has_bit(modes::GROUP_READ),
  696. group_write: has_bit(modes::GROUP_WRITE),
  697. group_execute: has_bit(modes::GROUP_EXECUTE),
  698. other_read: has_bit(modes::OTHER_READ),
  699. other_write: has_bit(modes::OTHER_WRITE),
  700. other_execute: has_bit(modes::OTHER_EXECUTE),
  701. sticky: has_bit(modes::STICKY),
  702. setgid: has_bit(modes::SETGID),
  703. setuid: has_bit(modes::SETUID),
  704. })
  705. }
  706. #[cfg(windows)]
  707. pub fn attributes(&self) -> f::Attributes {
  708. let bits = self.metadata.file_attributes();
  709. let has_bit = |bit| bits & bit == bit;
  710. // https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
  711. f::Attributes {
  712. directory: has_bit(0x10),
  713. archive: has_bit(0x20),
  714. readonly: has_bit(0x1),
  715. hidden: has_bit(0x2),
  716. system: has_bit(0x4),
  717. reparse_point: has_bit(0x400),
  718. }
  719. }
  720. /// This file’s security context field.
  721. pub fn security_context(&self) -> f::SecurityContext<'_> {
  722. let context = match self
  723. .extended_attributes()
  724. .iter()
  725. .find(|a| a.name == "security.selinux")
  726. {
  727. Some(attr) => f::SecurityContextType::SELinux(&attr.value),
  728. None => f::SecurityContextType::None,
  729. };
  730. f::SecurityContext { context }
  731. }
  732. }
  733. impl<'a> AsRef<File<'a>> for File<'a> {
  734. fn as_ref(&self) -> &File<'a> {
  735. self
  736. }
  737. }
  738. /// The result of following a symlink.
  739. pub enum FileTarget<'dir> {
  740. /// The symlink pointed at a file that exists.
  741. Ok(Box<File<'dir>>),
  742. /// The symlink pointed at a file that does not exist. Holds the path
  743. /// where the file would be, if it existed.
  744. Broken(PathBuf),
  745. /// There was an IO error when following the link. This can happen if the
  746. /// file isn’t a link to begin with, but also if, say, we don’t have
  747. /// permission to follow it.
  748. Err(io::Error),
  749. // Err is its own variant, instead of having the whole thing be inside an
  750. // `io::Result`, because being unable to follow a symlink is not a serious
  751. // error — we just display the error message and move on.
  752. }
  753. impl<'dir> FileTarget<'dir> {
  754. /// Whether this link doesn’t lead to a file, for whatever reason. This
  755. /// gets used to determine how to highlight the link in grid views.
  756. pub fn is_broken(&self) -> bool {
  757. matches!(self, Self::Broken(_) | Self::Err(_))
  758. }
  759. }
  760. /// More readable aliases for the permission bits exposed by libc.
  761. #[allow(trivial_numeric_casts)]
  762. #[cfg(unix)]
  763. mod modes {
  764. // The `libc::mode_t` type’s actual type varies, but the value returned
  765. // from `metadata.permissions().mode()` is always `u32`.
  766. pub type Mode = u32;
  767. pub const USER_READ: Mode = libc::S_IRUSR as Mode;
  768. pub const USER_WRITE: Mode = libc::S_IWUSR as Mode;
  769. pub const USER_EXECUTE: Mode = libc::S_IXUSR as Mode;
  770. pub const GROUP_READ: Mode = libc::S_IRGRP as Mode;
  771. pub const GROUP_WRITE: Mode = libc::S_IWGRP as Mode;
  772. pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode;
  773. pub const OTHER_READ: Mode = libc::S_IROTH as Mode;
  774. pub const OTHER_WRITE: Mode = libc::S_IWOTH as Mode;
  775. pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode;
  776. pub const STICKY: Mode = libc::S_ISVTX as Mode;
  777. pub const SETGID: Mode = libc::S_ISGID as Mode;
  778. pub const SETUID: Mode = libc::S_ISUID as Mode;
  779. }
  780. #[cfg(test)]
  781. mod ext_test {
  782. use super::File;
  783. use std::path::Path;
  784. #[test]
  785. fn extension() {
  786. assert_eq!(Some("dat".to_string()), File::ext(Path::new("fester.dat")))
  787. }
  788. #[test]
  789. fn dotfile() {
  790. assert_eq!(Some("vimrc".to_string()), File::ext(Path::new(".vimrc")))
  791. }
  792. #[test]
  793. fn no_extension() {
  794. assert_eq!(None, File::ext(Path::new("jarlsberg")))
  795. }
  796. }
  797. #[cfg(test)]
  798. mod filename_test {
  799. use super::File;
  800. use std::path::Path;
  801. #[test]
  802. fn file() {
  803. assert_eq!("fester.dat", File::filename(Path::new("fester.dat")))
  804. }
  805. #[test]
  806. fn no_path() {
  807. assert_eq!("foo.wha", File::filename(Path::new("/var/cache/foo.wha")))
  808. }
  809. #[test]
  810. fn here() {
  811. assert_eq!(".", File::filename(Path::new(".")))
  812. }
  813. #[test]
  814. fn there() {
  815. assert_eq!("..", File::filename(Path::new("..")))
  816. }
  817. #[test]
  818. fn everywhere() {
  819. assert_eq!("..", File::filename(Path::new("./..")))
  820. }
  821. #[test]
  822. #[cfg(unix)]
  823. fn topmost() {
  824. assert_eq!("/", File::filename(Path::new("/")))
  825. }
  826. }