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