10// MsgReader provides access to a message. Reads return the "msg_prefix" in the
11// database (typically received headers), followed by the on-disk msg file
12// contents. MsgReader is an io.Reader, io.ReaderAt and io.Closer.
13type MsgReader struct {
14 prefix []byte // First part of the message. Typically contains received headers.
15 path string // To on-disk message file.
16 size int64 // Total size of message, including prefix and contents from path.
17 offset int64 // Current reading offset.
18 f *os.File // Opened path, automatically opened after prefix has been read.
19 err error // If set, error to return for reads. Sets io.EOF for readers, but ReadAt ignores them.
22var errMsgClosed = errors.New("msg is closed")
24// FileMsgReader makes a MsgReader for an open file.
25// If initialization fails, reads will return the error.
26// Only call close on the returned MsgReader if you want to close msgFile.
27func FileMsgReader(prefix []byte, msgFile *os.File) *MsgReader {
28 mr := &MsgReader{prefix: prefix, path: msgFile.Name(), f: msgFile}
29 fi, err := msgFile.Stat()
34 mr.size = int64(len(prefix)) + fi.Size()
38// Read reads data from the msg, taking prefix and on-disk msg file into account.
39// The read offset is adjusted after the read.
40func (m *MsgReader) Read(buf []byte) (int, error) {
41 return m.read(buf, m.offset, false)
44// ReadAt reads data from the msg, taking prefix and on-disk msg file into account.
45// The read offset is not affected by ReadAt.
46func (m *MsgReader) ReadAt(buf []byte, off int64) (n int, err error) {
47 return m.read(buf, off, true)
50// read always fill buf as far as possible, for ReadAt semantics.
51func (m *MsgReader) read(buf []byte, off int64, pread bool) (int, error) {
52 // If a reader has consumed the file and reached EOF, further ReadAt must not return eof.
53 if m.err != nil && (!pread || m.err != io.EOF) {
58 // First attempt to read from m.prefix.
59 pn := int64(len(m.prefix)) - off
65 copy(buf[o:], m.prefix[int(off):int(off)+n])
74 // Now we need to read from file. Ensure it is open.
76 f, err := os.Open(m.path)
83 n, err := m.f.ReadAt(buf[o:], off-int64(len(m.prefix)))
87 if !pread || err != io.EOF {
95 if off > m.size && (m.err == nil || m.err == io.EOF) {
96 err = fmt.Errorf("on-disk message larger than expected (off %d, size %d)", off, m.size)
105 if off > m.size && (m.err == nil || m.err == io.EOF) {
106 m.err = fmt.Errorf("on-disk message larger than expected (off %d, size %d, prefix %d)", off, m.size, len(m.prefix))
111// Close ensures the msg file is closed. Further reads will fail.
112func (m *MsgReader) Close() error {
114 if err := m.f.Close(); err != nil {
119 if m.err == errMsgClosed {
126// Reset rewinds the offset and clears error conditions, making it usable as a fresh reader.
127func (m *MsgReader) Reset() {
132// Size returns the total size of the contents of the message.
133func (m *MsgReader) Size() int64 {