`minio.Object` violates `io.Seeker` contract
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 We have not seen any practical problems from this. Feel free to submit a PR if you consider this a priority issue.
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.