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