|
| 1 | +package directfile |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "io" |
| 6 | + "os" |
| 7 | +) |
| 8 | + |
| 9 | +// DirectFile is a wrapper around os.File that ensures aligned reads for O_DIRECT support, |
| 10 | +// even when trying to read small chunks |
| 11 | +type DirectFile struct { |
| 12 | + file *os.File |
| 13 | + blockSize uint32 |
| 14 | + |
| 15 | + // Direct indicates whether the current platform actually supported O_DIRECT when opening |
| 16 | + Direct bool |
| 17 | +} |
| 18 | + |
| 19 | +func openNodirect(path string) (*DirectFile, error) { |
| 20 | + // O_DIRECT is not supported on this platform, fallback to normal open |
| 21 | + fmt.Println("O_DIRECT not supported, using regular open") |
| 22 | + file, err := os.Open(path) |
| 23 | + if err != nil { |
| 24 | + return nil, fmt.Errorf("error calling os.Open on file: %w", err) |
| 25 | + } |
| 26 | + |
| 27 | + return &DirectFile{file: file, Direct: false}, nil |
| 28 | +} |
| 29 | + |
| 30 | +// Read satisfies the io.Reader |
| 31 | +func (df *DirectFile) Read(p []byte) (n int, err error) { |
| 32 | + blockSize := int(df.blockSize) |
| 33 | + |
| 34 | + // Ensure buffer length is aligned with BlockSize for O_DIRECT |
| 35 | + alignedSize := (len(p) / blockSize) * blockSize |
| 36 | + if alignedSize == 0 { |
| 37 | + return 0, fmt.Errorf("buffer size must be at least %d bytes for O_DIRECT", blockSize) |
| 38 | + } |
| 39 | + |
| 40 | + // Create an aligned buffer |
| 41 | + buf, err := alignedBuffer(alignedSize, blockSize) |
| 42 | + if err != nil { |
| 43 | + return 0, fmt.Errorf("failed to create aligned buffer: %w", err) |
| 44 | + } |
| 45 | + |
| 46 | + // Perform the read |
| 47 | + n, err = df.file.Read(buf) |
| 48 | + if n > len(p) { |
| 49 | + n = len(p) // Only copy as much as p can hold |
| 50 | + } |
| 51 | + copy(p, buf[:n]) |
| 52 | + return n, err |
| 53 | +} |
| 54 | + |
| 55 | +// ReadAt satisfies the io.ReaderAt interface |
| 56 | +func (df *DirectFile) ReadAt(p []byte, off int64) (n int, err error) { |
| 57 | + blockSize := int(df.blockSize) |
| 58 | + // Calculate aligned offset by rounding down to the nearest BlockSize boundary |
| 59 | + // Integer division in go always discards remainder |
| 60 | + alignedOffset := (int(off) / blockSize) * blockSize |
| 61 | + |
| 62 | + // Difference between aligned offset and requested offset |
| 63 | + // Need to read at least this many extra bytes, since we moved the starting point earlier this much |
| 64 | + offsetDiff := int(off) - alignedOffset |
| 65 | + |
| 66 | + // Calculate how much data to read to cover the requested segment, ensuring alignment |
| 67 | + alignedReadSize := ((len(p) + offsetDiff + blockSize - 1) / blockSize) * blockSize |
| 68 | + |
| 69 | + // Create an aligned buffer for the full read |
| 70 | + buf, err := alignedBuffer(alignedReadSize, blockSize) |
| 71 | + if err != nil { |
| 72 | + return 0, fmt.Errorf("failed to create aligned buffer: %w", err) |
| 73 | + } |
| 74 | + |
| 75 | + // Perform the read at the aligned offset |
| 76 | + n, err = df.file.ReadAt(buf, int64(alignedOffset)) |
| 77 | + if err != nil && err != io.EOF { |
| 78 | + return 0, err |
| 79 | + } |
| 80 | + |
| 81 | + // Calculate how much of the read buffer to copy into p |
| 82 | + copyLen := n - offsetDiff |
| 83 | + if copyLen > len(p) { |
| 84 | + copyLen = len(p) |
| 85 | + } else if copyLen < 0 { |
| 86 | + return 0, fmt.Errorf("read beyond end of file") |
| 87 | + } |
| 88 | + |
| 89 | + // Copy the relevant part of the buffer to p |
| 90 | + copy(p, buf[offsetDiff:offsetDiff+copyLen]) |
| 91 | + return copyLen, nil |
| 92 | +} |
0 commit comments