| 1 | // FUSE stat helpers. go-fuse only builds on linux, darwin, and freebsd. |
| 2 | //go:build (linux || darwin || freebsd) && !nofuse |
| 3 | |
| 4 | package mount |
| 5 | |
| 6 | import ( |
| 7 | "strconv" |
| 8 | "strings" |
| 9 | |
| 10 | "github.com/hanwen/go-fuse/v2/fuse" |
| 11 | ) |
| 12 | |
| 13 | // StatBlockSize is the POSIX stat(2) block unit. The st_blocks field |
| 14 | // reports allocation in 512-byte units regardless of the filesystem's |
| 15 | // real block size (see `man 2 stat`). Tools like `du`, `ls -s`, and |
| 16 | // `find -size` multiply st_blocks by this constant to compute bytes. |
| 17 | const StatBlockSize = 512 |
| 18 | |
| 19 | // DefaultBlksize is the preferred I/O size (stat.st_blksize) FUSE mounts |
| 20 | // advertise when no chunker-derived value applies (readonly /ipfs, or |
| 21 | // writable /mfs with a rabin/buzhash chunker). Larger hints let tools |
| 22 | // like cp, dd, and rsync use bigger buffers, amortizing FUSE syscall and |
| 23 | // DAG-walk overhead. 1 MiB matches the chunk size of Kubo's |
| 24 | // cross-implementation CID-deterministic import profile (IPIP-499). |
| 25 | // Hardcoded instead of tracking boxo's chunker default so the stat(2) |
| 26 | // contract stays stable across Kubo and boxo upgrades. |
| 27 | const DefaultBlksize = 1024 * 1024 |
| 28 | |
| 29 | // SizeToStatBlocks converts a byte size to the number of 512-byte blocks |
| 30 | // reported by POSIX stat(2) in the st_blocks field, rounded up so a |
| 31 | // non-empty file reports at least one block. |
| 32 | func SizeToStatBlocks(size uint64) uint64 { |
| 33 | return (size + StatBlockSize - 1) / StatBlockSize |
| 34 | } |
| 35 | |
| 36 | // BlksizeFromChunker derives the preferred I/O size hint for the writable |
| 37 | // mounts from the user's Import.UnixFSChunker setting. It extracts the |
| 38 | // byte count from `size-<bytes>` and returns DefaultBlksize for rabin, |
| 39 | // buzhash, or malformed values (where there is no single preferred size). |
| 40 | // Values are clamped to fuse.MAX_KERNEL_WRITE because the kernel splits |
| 41 | // any larger userspace read/write into MAX_KERNEL_WRITE-sized FUSE ops |
| 42 | // regardless, so hinting past the ceiling just wastes userspace buffers. |
| 43 | func BlksizeFromChunker(chunkerStr string) uint32 { |
| 44 | if sizeStr, ok := strings.CutPrefix(chunkerStr, "size-"); ok { |
| 45 | if size, err := strconv.ParseUint(sizeStr, 10, 64); err == nil && size > 0 { |
| 46 | if size > fuse.MAX_KERNEL_WRITE { |
| 47 | return fuse.MAX_KERNEL_WRITE |
| 48 | } |
| 49 | return uint32(size) |
| 50 | } |
| 51 | } |
| 52 | return DefaultBlksize |
| 53 | } |