minio-go icon indicating copy to clipboard operation
minio-go copied to clipboard

`minio.Object` violates `io.Seeker` contract

Open opengs opened this issue 3 months ago • 2 comments

Violation of io.Seeker contract

According to Go's io.Seeker documentation:

Seeking to an offset before the start of the file is an error. Seeking to any positive offset may be allowed, but if the new offset exceeds the size of the underlying object, the behavior of subsequent I/O operations is implementation-dependent.

Currently, minio.Object does not respect this contract (its not possible to seek to any positive offset), making it incompatible with standard Go I/O expectations.

How to reproduce

Use tests from standard library testing/iotest. minio.Object returned by GetObject will fail on those tests.

Currently, it fails with:

Seek(-1, 1) from EOF = 0, Negative position not allowed for 1, want 39, nil

The issue likely originates here:

https://github.com/minio/minio-go/blob/60b85ef162eb9faa881c431229d3c9fc40919c7f/api-get-object.go#L539

Since testing/iotest is part of the standard Go library, this behavior prevents minio.Object from being safely used in other libraries that rely on standard I/O behavior.

Proposed solution:

	// Negative offset not valid for whence of '0'.
	if offset < 0 && whence == 0 {
		return 0, errInvalidArgument("Negative position not allowed for 0")
	}

opengs avatar Sep 07 '25 19:09 opengs

@opengs We have not seen any practical problems from this. Feel free to submit a PR if you consider this a priority issue.

klauspost avatar Sep 07 '25 19:09 klauspost

Indeed, the problem is not really practical. My use case is that I'm creating a library that wraps underlying OS operations. I want to keep the interface as close to what Go offers as possible. For now, I'm using a workaround:

func (s *s3File) Seek(offset int64, whence int) (int64, error) {
	if offset < 0 && whence == io.SeekCurrent { // issue https://github.com/minio/minio-go/issues/2155
		// get the current offset
		currentOffset, err := s.object.Seek(0, io.SeekCurrent)
		if err != nil {
			return 0, err
		}
		// calculate the new offset
		newOffset := currentOffset + offset
		if newOffset < 0 {
			return 0, errors.New("negative offset is not allowed")
		}
		// seek to the new offset
		return s.object.Seek(newOffset, io.SeekStart)
	}

	return s.object.Seek(offset, whence)
}

For now, it works and passes both the tests from iotest and the tests from my project. The proposed solution should also work perfectly. I’ll take your suggestion and, if I have some time, create a PR.

opengs avatar Sep 08 '25 08:09 opengs