sonar

Scan files at memory bandwidth speed.
Log | Files | Refs

read_linux.odin (3828B)


      1 #+build linux
      2 package walk
      3 
      4 import "core:mem"
      5 import "core:strings"
      6 import "core:sys/linux"
      7 
      8 /*
      9 Read one directory with the calls the work actually needs.
     10 
     11 core:os describes every entry in full: an fd opened and closed around it, a statx on
     12 that fd, and the entry's path read back through /proc/self/fd. Four syscalls, where
     13 one statx against the directory answers everything the tree records. Walking 14,478
     14 entries cost 64,235 calls that way against du's 21,511 for the same tree, and the
     15 procfs readlink was over half of it.
     16 
     17 getdents also names each entry's type, so most are settled without asking again, and
     18 statx reports allocated blocks as readily as length. That makes the on-disk figure
     19 exact here rather than the length rounded up to a guessed block, so a sparse or
     20 compressed file is charged what it occupies.
     21 */
     22 @(private)
     23 read_dir :: proc(d: Dir, w: ^Worker) -> (nodes, bytes: u64, ok: bool) {
     24 	path: Path
     25 	cpath, path_ok := terminate(d.path, &path)
     26 	if !path_ok {
     27 		return 0, 0, false
     28 	}
     29 	// Not NOFOLLOW: the queue holds directories, and symlinks only when the caller
     30 	// asked for those to be followed, so refusing a link here would refuse exactly
     31 	// the ones it was told to walk. DIRECTORY still rejects a link onto a file.
     32 	fd, open_errno := linux.open(cpath, {.DIRECTORY, .CLOEXEC})
     33 	if open_errno != .NONE {
     34 		return 0, 0, false
     35 	}
     36 	defer linux.close(fd)
     37 
     38 	// Sized so a directory of any ordinary size comes back in one call. core:os
     39 	// starts at 512 bytes and grows only when the kernel refuses, which cost it
     40 	// roughly twice the getdents calls for the same tree.
     41 	buf: [32 * mem.Kilobyte]u8
     42 	for {
     43 		n, errno := linux.getdents(fd, buf[:])
     44 		if errno != .NONE {
     45 			return nodes, bytes, false
     46 		}
     47 		if n == 0 {
     48 			return nodes, bytes, true
     49 		}
     50 		offset := 0
     51 		for entry in linux.dirent_iterate_buf(buf[:n], &offset) {
     52 			name := linux.dirent_name(entry)
     53 			if name == "." || name == ".." {
     54 				continue
     55 			}
     56 			e := Entry {
     57 				name      = name,
     58 				directory = entry.type == .DIR,
     59 				symlink   = entry.type == .LNK,
     60 			}
     61 			// The name sits in the buffer already terminated, so it addresses the
     62 			// entry without being copied anywhere first.
     63 			mask := linux.Statx_Mask{.TYPE, .SIZE, .BLOCKS}
     64 			st: linux.Statx
     65 			if linux.statx(fd, cstring(raw_data(name)), {.SYMLINK_NOFOLLOW}, mask, &st) == .NONE {
     66 				// Some filesystems leave the type out of a directory entry. Those are
     67 				// the only ones that have to be told apart here.
     68 				if entry.type == .UNKNOWN {
     69 					e.directory = linux.S_ISDIR(st.mode)
     70 					e.symlink = linux.S_ISLNK(st.mode)
     71 				}
     72 				e.size = st.size
     73 				if !e.directory && !e.symlink {
     74 					// Blocks are counted in 512 byte units whatever the filesystem
     75 					// builds them from.
     76 					e.disk = st.blocks * 512
     77 				}
     78 			}
     79 			index, descend, recorded := record(w, d.index, e)
     80 			if !recorded {
     81 				return nodes, bytes, false
     82 			}
     83 			if descend {
     84 				child, join_err := join(d.path, name, w.allocator)
     85 				if join_err != nil {
     86 					w.err = .Out_Of_Memory
     87 					return nodes, bytes, false
     88 				}
     89 				append(&w.found, Dir{index = index, path = child})
     90 			}
     91 			nodes += 1
     92 			bytes += e.disk
     93 		}
     94 	}
     95 }
     96 
     97 // A path as the kernel wants it. Long enough for anything the kernel will accept, so
     98 // building one costs no allocation.
     99 @(private)
    100 Path :: [4096]u8
    101 
    102 @(private)
    103 terminate :: proc(path: string, buf: ^Path) -> (cstring, bool) {
    104 	if len(path) >= len(buf) {
    105 		return nil, false
    106 	}
    107 	copy(buf[:], path)
    108 	buf[len(path)] = 0
    109 	return cstring(&buf[0]), true
    110 }
    111 
    112 // The child's path, which getdents does not hand back and the queue needs.
    113 @(private)
    114 join :: proc(dir, name: string, allocator: mem.Allocator) -> (string, mem.Allocator_Error) {
    115 	if strings.has_suffix(dir, "/") {
    116 		return strings.concatenate({dir, name}, allocator)
    117 	}
    118 	return strings.concatenate({dir, "/", name}, allocator)
    119 }