1 use std::fs::File;
2 use std::os::unix::fs::symlink;
3 use std::os::unix::prelude::AsRawFd;
4
5 use libc::{S_IFMT, S_IFLNK};
6
7 use nix::fcntl;
8 use nix::sys::stat::{self, fchmod, fchmodat, fstat, lstat, stat};
9 use nix::sys::stat::{FileStat, Mode, FchmodatFlags};
10 use nix::unistd::chdir;
11 use nix::Result;
12 use tempdir::TempDir;
13
14 #[allow(unused_comparisons)]
15 // uid and gid are signed on Windows, but not on other platforms. This function
16 // allows warning free compiles on all platforms, and can be removed when
17 // expression-level #[allow] is available.
valid_uid_gid(stat: FileStat) -> bool18 fn valid_uid_gid(stat: FileStat) -> bool {
19 // uid could be 0 for the `root` user. This quite possible when
20 // the tests are being run on a rooted Android device.
21 stat.st_uid >= 0 && stat.st_gid >= 0
22 }
23
assert_stat_results(stat_result: Result<FileStat>)24 fn assert_stat_results(stat_result: Result<FileStat>) {
25 let stats = stat_result.expect("stat call failed");
26 assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent
27 assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent
28 assert!(stats.st_mode > 0); // must be positive integer
29 assert!(stats.st_nlink == 1); // there links created, must be 1
30 assert!(valid_uid_gid(stats)); // must be positive integers
31 assert!(stats.st_size == 0); // size is 0 because we did not write anything to the file
32 assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent
33 assert!(stats.st_blocks <= 16); // Up to 16 blocks can be allocated for a blank file
34 }
35
assert_lstat_results(stat_result: Result<FileStat>)36 fn assert_lstat_results(stat_result: Result<FileStat>) {
37 let stats = stat_result.expect("stat call failed");
38 assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent
39 assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent
40 assert!(stats.st_mode > 0); // must be positive integer
41
42 // st_mode is c_uint (u32 on Android) while S_IFMT is mode_t
43 // (u16 on Android), and that will be a compile error.
44 // On other platforms they are the same (either both are u16 or u32).
45 assert!((stats.st_mode as usize) & (S_IFMT as usize) == S_IFLNK as usize); // should be a link
46 assert!(stats.st_nlink == 1); // there links created, must be 1
47 assert!(valid_uid_gid(stats)); // must be positive integers
48 assert!(stats.st_size > 0); // size is > 0 because it points to another file
49 assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent
50
51 // st_blocks depends on whether the machine's file system uses fast
52 // or slow symlinks, so just make sure it's not negative
53 // (Android's st_blocks is ulonglong which is always non-negative.)
54 assert!(stats.st_blocks >= 0);
55 }
56
57 #[test]
test_stat_and_fstat()58 fn test_stat_and_fstat() {
59 let tempdir = TempDir::new("nix-test_stat_and_fstat").unwrap();
60 let filename = tempdir.path().join("foo.txt");
61 let file = File::create(&filename).unwrap();
62
63 let stat_result = stat(&filename);
64 assert_stat_results(stat_result);
65
66 let fstat_result = fstat(file.as_raw_fd());
67 assert_stat_results(fstat_result);
68 }
69
70 #[test]
test_fstatat()71 fn test_fstatat() {
72 let tempdir = TempDir::new("nix-test_fstatat").unwrap();
73 let filename = tempdir.path().join("foo.txt");
74 File::create(&filename).unwrap();
75 let dirfd = fcntl::open(tempdir.path(),
76 fcntl::OFlag::empty(),
77 stat::Mode::empty());
78
79 let result = stat::fstatat(dirfd.unwrap(),
80 &filename,
81 fcntl::AtFlags::empty());
82 assert_stat_results(result);
83 }
84
85 #[test]
test_stat_fstat_lstat()86 fn test_stat_fstat_lstat() {
87 let tempdir = TempDir::new("nix-test_stat_fstat_lstat").unwrap();
88 let filename = tempdir.path().join("bar.txt");
89 let linkname = tempdir.path().join("barlink");
90
91 File::create(&filename).unwrap();
92 symlink("bar.txt", &linkname).unwrap();
93 let link = File::open(&linkname).unwrap();
94
95 // should be the same result as calling stat,
96 // since it's a regular file
97 let stat_result = stat(&filename);
98 assert_stat_results(stat_result);
99
100 let lstat_result = lstat(&linkname);
101 assert_lstat_results(lstat_result);
102
103 let fstat_result = fstat(link.as_raw_fd());
104 assert_stat_results(fstat_result);
105 }
106
107 #[test]
test_fchmod()108 fn test_fchmod() {
109 let tempdir = TempDir::new("nix-test_fchmod").unwrap();
110 let filename = tempdir.path().join("foo.txt");
111 let file = File::create(&filename).unwrap();
112
113 let mut mode1 = Mode::empty();
114 mode1.insert(Mode::S_IRUSR);
115 mode1.insert(Mode::S_IWUSR);
116 fchmod(file.as_raw_fd(), mode1).unwrap();
117
118 let file_stat1 = stat(&filename).unwrap();
119 assert_eq!(file_stat1.st_mode & 0o7777, mode1.bits());
120
121 let mut mode2 = Mode::empty();
122 mode2.insert(Mode::S_IROTH);
123 fchmod(file.as_raw_fd(), mode2).unwrap();
124
125 let file_stat2 = stat(&filename).unwrap();
126 assert_eq!(file_stat2.st_mode & 0o7777, mode2.bits());
127 }
128
129 #[test]
test_fchmodat()130 fn test_fchmodat() {
131 let tempdir = TempDir::new("nix-test_fchmodat").unwrap();
132 let filename = "foo.txt";
133 let fullpath = tempdir.path().join(filename);
134 File::create(&fullpath).unwrap();
135
136 let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
137
138 let mut mode1 = Mode::empty();
139 mode1.insert(Mode::S_IRUSR);
140 mode1.insert(Mode::S_IWUSR);
141 fchmodat(Some(dirfd), filename, mode1, FchmodatFlags::FollowSymlink).unwrap();
142
143 let file_stat1 = stat(&fullpath).unwrap();
144 assert_eq!(file_stat1.st_mode & 0o7777, mode1.bits());
145
146 chdir(tempdir.path()).unwrap();
147
148 let mut mode2 = Mode::empty();
149 mode2.insert(Mode::S_IROTH);
150 fchmodat(None, filename, mode2, FchmodatFlags::FollowSymlink).unwrap();
151
152 let file_stat2 = stat(&fullpath).unwrap();
153 assert_eq!(file_stat2.st_mode & 0o7777, mode2.bits());
154 }
155