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