dirfdfs.ha (11176B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use encoding::utf8; 5 use errors; 6 use fs; 7 use io; 8 use path; 9 use rt; 10 use strings; 11 use time; 12 use types::c; 13 14 type os_filesystem = struct { 15 fs: fs::fs, 16 dirfd: int, 17 getdents_bufsz: size, 18 }; 19 20 // Opens a file descriptor as an [[fs::fs]]. This file descriptor must be a 21 // directory file. The file will be closed when the fs is closed. 22 export fn dirfdopen(fd: io::file) *fs::fs = { 23 let ofs = alloc(os_filesystem { ... }); 24 let fs = static_dirfdopen(fd, ofs); 25 fs.close = &fs_close; 26 return fs; 27 }; 28 29 fn static_dirfdopen(fd: io::file, filesystem: *os_filesystem) *fs::fs = { 30 *filesystem = os_filesystem { 31 fs = fs::fs { 32 open = &fs_open, 33 openfile = &fs_open_file, 34 create = &fs_create, 35 createfile = &fs_create_file, 36 remove = &fs_remove, 37 rename = &fs_rename, 38 iter = &fs_iter, 39 stat = &fs_stat, 40 readlink = &fs_readlink, 41 mkdir = &fs_mkdir, 42 rmdir = &fs_rmdir, 43 chmod = &fs_chmod, 44 chown = &fs_chown, 45 chtimes = &fs_chtimes, 46 fchtimes = &fs_fchtimes, 47 resolve = &fs_resolve, 48 ... 49 }, 50 dirfd = fd, 51 getdents_bufsz = 32768, // 32 KiB 52 }; 53 return &filesystem.fs; 54 }; 55 56 // Sets the buffer size to use with the getdents(2) system call, for use with 57 // [[fs::iter]]. A larger buffer requires a larger runtime allocation, but can 58 // scan large directories faster. The default buffer size is 32 KiB. 59 // 60 // This function is not portable. 61 export fn dirfdfs_set_getdents_bufsz(fs: *fs::fs, sz: size) void = { 62 assert(fs.open == &fs_open); 63 let fs = fs: *os_filesystem; 64 fs.getdents_bufsz = sz; 65 }; 66 67 // Returns an [[io::file]] for this filesystem. This function is not portable. 68 export fn dirfile(fs: *fs::fs) io::file = { 69 assert(fs.open == &fs_open); 70 let fs = fs: *os_filesystem; 71 return fs.dirfd; 72 }; 73 74 fn errno_to_fs(err: rt::errno) fs::error = { 75 switch (err) { 76 case rt::ENOENT => 77 return errors::noentry; 78 case rt::EEXIST => 79 return errors::exists; 80 case rt::EACCES => 81 return errors::noaccess; 82 case rt::EBUSY => 83 return errors::busy; 84 case rt::ENOTDIR => 85 return fs::wrongtype; 86 case rt::EOPNOTSUPP, rt::ENOSYS => 87 return errors::unsupported; 88 case rt::EXDEV => 89 return fs::cannotrename; 90 case => 91 return errors::errno(err); 92 }; 93 }; 94 95 fn _fs_open( 96 fs: *fs::fs, 97 path: str, 98 flags: int, 99 mode: uint, 100 ) (io::file | fs::error) = { 101 let fs = fs: *os_filesystem; 102 103 let fd = match (rt::openat(fs.dirfd, path, flags, mode)) { 104 case let err: rt::errno => 105 return errno_to_fs(err); 106 case let fd: int => 107 yield fd; 108 }; 109 110 return io::fdopen(fd); 111 }; 112 113 fn fsflags_to_bsd(flags: fs::flag) (int | errors::unsupported) = { 114 let out = rt::O_NOCTTY | rt::O_CLOEXEC; 115 if (flags & fs::flag::WRONLY == fs::flag::WRONLY) { 116 out |= rt::O_WRONLY; 117 }; 118 if (flags & fs::flag::RDWR == fs::flag::RDWR) { 119 out |= rt::O_RDWR; 120 }; 121 if (flags & fs::flag::CREATE == fs::flag::CREATE) { 122 out |= rt::O_CREAT; 123 }; 124 if (flags & fs::flag::EXCL == fs::flag::EXCL) { 125 out |= rt::O_EXCL; 126 }; 127 if (flags & fs::flag::CTTY == fs::flag::CTTY) { 128 out &= ~rt::O_NOCTTY; 129 }; 130 if (flags & fs::flag::TRUNC == fs::flag::TRUNC) { 131 out |= rt::O_TRUNC; 132 }; 133 if (flags & fs::flag::APPEND == fs::flag::APPEND) { 134 out |= rt::O_APPEND; 135 }; 136 if (flags & fs::flag::NONBLOCK == fs::flag::NONBLOCK) { 137 out |= rt::O_NONBLOCK; 138 }; 139 if (flags & fs::flag::DSYNC == fs::flag::DSYNC) { 140 out |= rt::O_DSYNC; 141 }; 142 if (flags & fs::flag::SYNC == fs::flag::SYNC) { 143 out |= rt::O_SYNC; 144 }; 145 if (flags & fs::flag::RSYNC == fs::flag::RSYNC) { 146 out |= rt::O_SYNC; 147 }; 148 if (flags & fs::flag::DIRECTORY == fs::flag::DIRECTORY) { 149 out |= rt::O_DIRECTORY; 150 }; 151 if (flags & fs::flag::NOFOLLOW == fs::flag::NOFOLLOW) { 152 out |= rt::O_NOFOLLOW; 153 }; 154 if (flags & fs::flag::NOCLOEXEC == fs::flag::NOCLOEXEC) { 155 out &= ~rt::O_CLOEXEC; 156 }; 157 if (flags & fs::flag::PATH == fs::flag::PATH 158 || flags & fs::flag::NOATIME == fs::flag::NOATIME 159 || flags & fs::flag::TMPFILE == fs::flag::TMPFILE) { 160 return errors::unsupported; 161 }; 162 return out; 163 }; 164 165 fn fs_open_file( 166 fs: *fs::fs, 167 path: str, 168 flags: fs::flag, 169 ) (io::file | fs::error) = { 170 return _fs_open(fs, path, fsflags_to_bsd(flags)?, 0); 171 }; 172 173 fn fs_open( 174 fs: *fs::fs, 175 path: str, 176 flags: fs::flag, 177 ) (io::handle | fs::error) = fs_open_file(fs, path, flags)?; 178 179 fn fs_create_file( 180 fs: *fs::fs, 181 path: str, 182 mode: fs::mode, 183 flags: fs::flag, 184 ) (io::file | fs::error) = { 185 flags |= fs::flag::CREATE; 186 return _fs_open(fs, path, fsflags_to_bsd(flags)?, mode)?; 187 }; 188 189 fn fs_create( 190 fs: *fs::fs, 191 path: str, 192 mode: fs::mode, 193 flags: fs::flag, 194 ) (io::handle | fs::error) = { 195 return fs_create_file(fs, path, mode, flags)?; 196 }; 197 198 fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = { 199 let fs = fs: *os_filesystem; 200 match (rt::unlinkat(fs.dirfd, path, 0)) { 201 case let err: rt::errno => 202 return errno_to_fs(err); 203 case void => void; 204 }; 205 }; 206 207 fn fs_rename(fs: *fs::fs, oldpath: str, newpath: str) (void | fs::error) = { 208 let fs = fs: *os_filesystem; 209 match (rt::renameat(fs.dirfd, oldpath, fs.dirfd, newpath)) { 210 case let err: rt::errno => 211 return errno_to_fs(err); 212 case void => void; 213 }; 214 }; 215 216 fn fs_stat(fs: *fs::fs, path: str) (fs::filestat | fs::error) = { 217 let fs = fs: *os_filesystem; 218 let st = rt::st { ... }; 219 match (rt::fstatat(fs.dirfd, path, &st, rt::AT_SYMLINK_NOFOLLOW)) { 220 case let err: rt::errno => 221 return errno_to_fs(err); 222 case void => void; 223 }; 224 return fs::filestat { 225 mask = fs::stat_mask::UID 226 | fs::stat_mask::GID 227 | fs::stat_mask::SIZE 228 | fs::stat_mask::INODE 229 | fs::stat_mask::ATIME 230 | fs::stat_mask::MTIME 231 | fs::stat_mask::CTIME, 232 mode = st.mode: fs::mode, 233 uid = st.uid, 234 gid = st.gid, 235 sz = st.sz: size, 236 inode = st.ino, 237 atime = time::instant { 238 sec = st.atime.tv_sec, 239 nsec = st.atime.tv_nsec, 240 }, 241 mtime = time::instant { 242 sec = st.mtime.tv_sec, 243 nsec = st.mtime.tv_nsec, 244 }, 245 ctime = time::instant { 246 sec = st.ctime.tv_sec, 247 nsec = st.ctime.tv_nsec, 248 }, 249 }; 250 }; 251 252 fn fs_readlink(fs: *fs::fs, path: str) (str | fs::error) = { 253 let fs = fs: *os_filesystem; 254 static let buf: [rt::PATH_MAX]u8 = [0...]; 255 let z = match (rt::readlinkat(fs.dirfd, path, buf[..])) { 256 case let err: rt::errno => 257 switch (err) { 258 case rt::EINVAL => 259 return fs::wrongtype; 260 case => 261 return errno_to_fs(err); 262 }; 263 case let z: size => 264 yield z; 265 }; 266 return strings::fromutf8(buf[..z])!; 267 }; 268 269 fn fs_rmdir(fs: *fs::fs, path: str) (void | fs::error) = { 270 let fs = fs: *os_filesystem; 271 match (rt::unlinkat(fs.dirfd, path, rt::AT_REMOVEDIR)) { 272 case let err: rt::errno => 273 return errno_to_fs(err); 274 case void => void; 275 }; 276 }; 277 278 fn fs_mkdir(fs: *fs::fs, path: str, mode: fs::mode) (void | fs::error) = { 279 let fs = fs: *os_filesystem; 280 match (rt::mkdirat(fs.dirfd, path, mode: uint)) { 281 case let err: rt::errno => 282 switch (err) { 283 case rt::EISDIR => 284 return errors::exists; 285 case => 286 return errno_to_fs(err); 287 }; 288 case void => void; 289 }; 290 }; 291 292 fn fs_chmod(fs: *fs::fs, path: str, mode: fs::mode) (void | fs::error) = { 293 let fs = fs: *os_filesystem; 294 match (rt::fchmodat(fs.dirfd, path, mode: uint, 0)) { 295 case let err: rt::errno => 296 return errno_to_fs(err); 297 case void => void; 298 }; 299 }; 300 301 fn fs_fchmod(fd: io::file, mode: fs::mode) (void | fs::error) = { 302 match (rt::fchmod(fd, mode: uint)) { 303 case let err: rt::errno => 304 return errno_to_fs(err); 305 case void => void; 306 }; 307 }; 308 309 fn fs_chown(fs: *fs::fs, path: str, uid: uint, gid: uint) (void | fs::error) = { 310 let fs = fs: *os_filesystem; 311 match (rt::fchownat(fs.dirfd, path, uid, gid, 0)) { 312 case let err: rt::errno => 313 return errno_to_fs(err); 314 case void => void; 315 }; 316 }; 317 318 fn fs_fchown(fd: io::file, uid: uint, gid: uint) (void | fs::error) = { 319 match (rt::fchown(fd, uid, gid)) { 320 case let err: rt::errno => 321 return errno_to_fs(err); 322 case void => void; 323 }; 324 }; 325 326 fn instant_to_timespec(time: (time::instant | void)) rt::timespec = { 327 match (time) { 328 case let t: time::instant => 329 return time::instant_to_timespec(t); 330 case void => 331 return rt::timespec{ 332 tv_sec = rt::UTIME_OMIT, 333 tv_nsec = rt::UTIME_OMIT 334 }; 335 }; 336 }; 337 338 fn fs_chtimes(fs: *fs::fs, path: str, atime: (time::instant | void), 339 mtime: (time::instant | void)) (void | fs::error) = { 340 let utimes: [2]rt::timespec = [ 341 instant_to_timespec(atime), 342 instant_to_timespec(mtime), 343 ]; 344 let fs = fs: *os_filesystem; 345 match (rt::utimensat(fs.dirfd, path, &utimes, 0)) { 346 case let err: rt::errno => 347 return errno_to_fs(err); 348 case void => void; 349 }; 350 }; 351 352 fn fs_fchtimes(fd: io::file, atime: (time::instant | void), 353 mtime: (time::instant | void)) (void | fs::error) = { 354 let utimes: [2]rt::timespec = [ 355 instant_to_timespec(atime), 356 instant_to_timespec(mtime), 357 ]; 358 match (rt::futimens(fd, &utimes)) { 359 case let err: rt::errno => 360 return errno_to_fs(err); 361 case void => void; 362 }; 363 }; 364 365 fn fs_resolve(fs: *fs::fs, path: str) str = { 366 if (path::abs(path)) { 367 return path; 368 }; 369 // XXX: This approach might not be right if this fs is based on a subdir 370 static let buf = path::buffer { ... }; 371 path::set(&buf, getcwd(), path)!; 372 return path::string(&buf); 373 }; 374 375 fn fs_close(fs: *fs::fs) void = { 376 let fs = fs: *os_filesystem; 377 rt::close(fs.dirfd)!; 378 }; 379 380 // Based on musl's readdir 381 type os_iterator = struct { 382 iter: fs::iterator, 383 fd: int, 384 buf_pos: size, 385 buf_end: size, 386 buf: []u8, 387 }; 388 389 fn fs_iter(fs: *fs::fs, path: str) (*fs::iterator | fs::error) = { 390 let fs = fs: *os_filesystem; 391 let flags = rt::O_RDONLY | rt::O_CLOEXEC | rt::O_DIRECTORY; 392 let fd: int = match (rt::openat(fs.dirfd, path, flags, 0)) { 393 case let err: rt::errno => 394 return errno_to_fs(err); 395 case let fd: int => 396 yield fd; 397 }; 398 399 let buf = match (rt::malloc(fs.getdents_bufsz)) { 400 case let v: *opaque => 401 yield v: *[*]u8; 402 case null => 403 return errors::nomem; 404 }; 405 let iter = alloc(os_iterator { 406 iter = fs::iterator { 407 next = &iter_next, 408 finish = &iter_finish, 409 }, 410 fd = fd, 411 buf = buf[..fs.getdents_bufsz], 412 ... 413 }); 414 return &iter.iter; 415 }; 416 417 fn iter_next(iter: *fs::iterator) (fs::dirent | done | fs::error) = { 418 let iter = iter: *os_iterator; 419 if (iter.buf_pos >= iter.buf_end) { 420 let n = match (rt::getdents(iter.fd, 421 iter.buf: *[*]u8, len(iter.buf))) { 422 case let err: rt::errno => 423 return errno_to_fs(err); 424 case let n: size => 425 yield n; 426 }; 427 if (n == 0) { 428 return done; 429 }; 430 iter.buf_end = n; 431 iter.buf_pos = 0; 432 }; 433 let de = &iter.buf[iter.buf_pos]: *rt::freebsd11_dirent; 434 iter.buf_pos += de.d_reclen; 435 let name = c::tostr(&de.d_name: *const c::char)?; 436 if (name == "." || name == "..") { 437 return iter_next(iter); 438 }; 439 440 let ftype: fs::mode = switch (de.d_type) { 441 case rt::DT_UNKNOWN => 442 yield fs::mode::UNKNOWN; 443 case rt::DT_FIFO => 444 yield fs::mode::FIFO; 445 case rt::DT_CHR => 446 yield fs::mode::CHR; 447 case rt::DT_DIR => 448 yield fs::mode::DIR; 449 case rt::DT_BLK => 450 yield fs::mode::BLK; 451 case rt::DT_REG => 452 yield fs::mode::REG; 453 case rt::DT_LNK => 454 yield fs::mode::LINK; 455 case rt::DT_SOCK => 456 yield fs::mode::SOCK; 457 case => 458 yield fs::mode::UNKNOWN; 459 }; 460 return fs::dirent { 461 name = name, 462 ftype = ftype, 463 }; 464 }; 465 466 fn iter_finish(iter: *fs::iterator) void = { 467 let iter = iter: *os_iterator; 468 rt::close(iter.fd)!; 469 free(iter.buf); 470 free(iter); 471 };