E-MailRelay
gfilestore.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gfilestore.cpp
19///
20
21#include "gdef.h"
22#include "gfilestore.h"
23#include "gnewfile.h"
24#include "gstoredfile.h"
25#include "gprocess.h"
26#include "gdirectory.h"
27#include "gformat.h"
28#include "ggettext.h"
29#include "groot.h"
30#include "gpath.h"
31#include "gfile.h"
32#include "gstr.h"
33#include "gtest.h"
34#include "glog.h"
35#include <iostream>
36#include <fstream>
37
38namespace GStore
39{
40 class FileIterator ;
41}
42
43class GStore::FileIterator : public MessageStore::Iterator /// A GStore::MessageStore::Iterator for GStore::FileStore.
44{
45public:
46 FileIterator( FileStore & store , const G::Path & dir , bool lock ) ;
47 ~FileIterator() override ;
48
49private: // overrides
50 std::unique_ptr<StoredMessage> next() override ;
51
52public:
53 FileIterator( const FileIterator & ) = delete ;
54 FileIterator( FileIterator && ) = delete ;
55 FileIterator & operator=( const FileIterator & ) = delete ;
56 FileIterator & operator=( FileIterator && ) = delete ;
57
58private:
59 FileStore & m_store ;
60 G::DirectoryList m_iter ;
61 bool m_lock ;
62} ;
63
64// ===
65
66GStore::FileIterator::FileIterator( FileStore & store , const G::Path & dir , bool lock ) :
67 m_store(store) ,
68 m_lock(lock)
69{
70 DirectoryReader claim_reader ;
71 m_iter.readType( dir , ".envelope" ) ;
72}
73
74GStore::FileIterator::~FileIterator()
75= default;
76
77std::unique_ptr<GStore::StoredMessage> GStore::FileIterator::next()
78{
79 while( m_iter.more() )
80 {
81 GStore::MessageId message_id( m_iter.filePath().withoutExtension().basename() ) ;
82 if( !message_id.valid() )
83 continue ;
84
85 auto message_ptr = std::make_unique<StoredFile>( m_store , message_id ) ;
86
87 if( m_lock && !message_ptr->lock() )
88 {
89 G_WARNING( "GStore::MessageStore: cannot lock file: \"" << m_iter.filePath().basename() << "\"" ) ;
90 continue ;
91 }
92
93 bool ok = false ;
94 std::string reason ;
95 ok = message_ptr->readEnvelope( reason ) && message_ptr->openContent( reason ) ;
96 if( !ok )
97 {
98 G_WARNING( "GStore::MessageStore: ignoring \"" << m_iter.filePath() << "\": " << reason ) ;
99 continue ;
100 }
101
102 return message_ptr ;
103 }
104 return {} ;
105}
106
107// ===
108
109GStore::FileStore::FileStore( const G::Path & dir , const G::Path & delivery_dir , const Config & config ) :
110 m_seq(config.seq) ,
111 m_dir(dir) ,
112 m_delivery_dir(delivery_dir) ,
113 m_config(config)
114{
115 checkPath( dir ) ;
116 osinit() ;
117}
118
120{
121 return m_dir ;
122}
123
125{
126 return m_delivery_dir.empty() ? m_dir : m_delivery_dir ;
127}
128
130{
131 return "X-MailRelay-" ;
132}
133
134std::string GStore::FileStore::format( int generation )
135{
136 // use a weird prefix to help with file(1) and magic(5)
137 if( generation == -5 )
138 return "#2821.3" ; // original
139 else if( generation == -4 )
140 return "#2821.4" ; // new for 1.9
141 else if( generation == -3 )
142 return "#2821.5" ; // new for 2.0
143 else if( generation == -2 )
144 return "#2821.6" ; // new for 2.4
145 else if( generation == -1 )
146 return "#2821.7" ; // new for 2.5rc
147 else
148 return "#2821.8" ; // new for 2.5
149}
150
151bool GStore::FileStore::knownFormat( const std::string & format_in )
152{
153 return
154 format_in == format(0) ||
155 format_in == format(-1) ||
156 format_in == format(-2) ||
157 format_in == format(-3) ||
158 format_in == format(-4) ||
159 format_in == format(-5) ;
160}
161
162void GStore::FileStore::checkPath( const G::Path & directory_path )
163{
164 G::Directory dir_test( directory_path ) ;
165 bool ok = false ;
166 int error = 0 ;
167
168 // fail if not readable (after switching effective userid)
169 {
170 FileWriter claim_writer ;
171 error = dir_test.usable() ;
172 ok = error == 0 ;
173 }
174 if( !ok )
175 {
176 throw InvalidDirectory( directory_path.str() , G::Process::strerror(error) ) ;
177 }
178
179 // warn if not writeable (after switching effective userid)
180 {
181 std::string tmp_filename = G::Directory::tmp() ;
182 FileWriter claim_writer ;
183 ok = dir_test.writeable( tmp_filename ) ;
184 }
185 if( !ok )
186 {
187 using G::format ;
188 using G::txt ;
189 G_WARNING( "GStore::MessageStore: " << format(txt("directory not writable: \"%1%\"")) % directory_path ) ;
190 }
191}
192
193std::string GStore::FileStore::location( const MessageId & id ) const
194{
195 return envelopePath(id).str() ;
196}
197
198std::unique_ptr<std::ofstream> GStore::FileStore::stream( const G::Path & path )
199{
200 auto stream_ptr = std::make_unique<std::ofstream>() ;
201 FileOp::openOut( *stream_ptr , path ) ;
202 return stream_ptr ;
203}
204
206{
207 return envelopePath(id).withExtension( "content" ) ;
208}
209
210G::Path GStore::FileStore::envelopePath( const MessageId & id , State state ) const
211{
212 if( state == State::New )
213 return m_dir / id.str().append(".envelope.new") ;
214 else if( state == State::Locked )
215 return m_dir / id.str().append(".envelope.busy") ;
216 else if( state == State::Bad )
217 return m_dir / id.str().append(".envelope.bad") ;
218 else
219 return m_dir / id.str().append(".envelope") ;
220}
221
223{
224 m_seq++ ;
225 if( m_seq == 0UL )
226 m_seq++ ;
227 return newId( m_seq ) ;
228}
229
231{
232 unsigned long timestamp = static_cast<unsigned long>(G::SystemTime::now().s()) ;
233 std::ostringstream ss ;
234 ss << "emailrelay." << G::Process::Id().str() << "." << timestamp << "." << seq ;
235 return MessageId( ss.str() ) ;
236}
237
238bool GStore::FileStore::empty() const
239{
240 G::DirectoryList list ;
241 DirectoryReader claim_reader ;
242 list.readType( m_dir , ".envelope" , 1U ) ;
243 const bool no_more = !list.more() ;
244 return no_more ;
245}
246
247std::vector<GStore::MessageId> GStore::FileStore::ids()
248{
249 G::DirectoryList list ;
250 {
251 DirectoryReader claim_reader ;
252 list.readType( m_dir , ".envelope" ) ;
253 }
254 std::vector<GStore::MessageId> result ;
255 while( list.more() )
256 result.emplace_back( list.filePath().withoutExtension().basename() ) ;
257 return result ;
258}
259
260std::vector<GStore::MessageId> GStore::FileStore::failures()
261{
262 G::DirectoryList list ;
263 {
264 DirectoryReader claim_reader ;
265 list.readType( m_dir , ".envelope.bad" ) ;
266 }
267 std::vector<GStore::MessageId> result ;
268 while( list.more() )
269 result.emplace_back( list.filePath().withoutExtension().withoutExtension().basename() ) ;
270 return result ;
271}
272
273void GStore::FileStore::unfailAll()
274{
275 G::DirectoryList list ;
276 {
277 DirectoryReader claim_reader ;
278 list.readType( m_dir , ".envelope.bad" ) ;
279 }
280 while( list.more() )
281 {
282 FileWriter claim_writer ;
283 FileOp::rename( list.filePath() , list.filePath().withoutExtension() ) ; // ignore errors
284 }
285}
286
287std::unique_ptr<GStore::MessageStore::Iterator> GStore::FileStore::iterator( bool lock )
288{
289 return std::make_unique<FileIterator>( *this , m_dir , lock ) ;
290}
291
292std::unique_ptr<GStore::StoredMessage> GStore::FileStore::get( const MessageId & id )
293{
294 auto message = std::make_unique<StoredFile>( *this , id ) ;
295 if( !message->lock() )
296 throw GetError( id.str().append(": cannot lock the envelope file") ) ;
297
298 std::string reason ;
299 if( !message->readEnvelope( reason ) )
300 throw GetError( id.str().append(": cannot read the envelope: ").append(reason) ) ;
301
302 if( !message->openContent( reason ) )
303 throw GetError( id.str().append(": cannot read the content: ").append(reason) ) ;
304
305 return message ;
306}
307
308GStore::Envelope GStore::FileStore::readEnvelope( const G::Path & envelope_path , std::ifstream * stream_p )
309{
310 std::ifstream strm ;
311 std::ifstream & envelope_stream = stream_p ? *stream_p : strm ;
312 if( !FileOp::openIn( envelope_stream , envelope_path ) )
313 throw EnvelopeReadError( envelope_path.str() , G::Process::strerror(FileOp::errno_()) ) ;
314
315 GStore::Envelope envelope ;
316 GStore::Envelope::read( envelope_stream , envelope ) ;
317 return envelope ;
318}
319
320std::unique_ptr<GStore::NewMessage> GStore::FileStore::newMessage( const std::string & from ,
321 const MessageStore::SmtpInfo & smtp_info , const std::string & from_auth_out )
322{
323 return std::make_unique<NewFile>( *this , from , smtp_info , from_auth_out , m_config.max_size ) ;
324}
325
326void GStore::FileStore::updated()
327{
328 G_DEBUG( "GStore::FileStore::updated" ) ;
329 m_update_signal.emit() ;
330}
331
332G::Slot::Signal<> & GStore::FileStore::messageStoreUpdateSignal() noexcept
333{
334 return m_update_signal ;
335}
336
337G::Slot::Signal<> & GStore::FileStore::messageStoreRescanSignal() noexcept
338{
339 return m_rescan_signal ;
340}
341
342void GStore::FileStore::rescan()
343{
344 messageStoreRescanSignal().emit() ;
345}
346
347// ===
348
350= default;
351
353= default;
354
355// ===
356
358= default;
359
361= default;
362
363// ===
364
366 G::Root(false) ,
367 G::Process::Umask(G::Process::Umask::Mode::Tighter)
368{
369}
370
372= default;
373
374// ===
375
376int & GStore::FileStore::FileOp::errno_() noexcept
377{
378 static int e {} ;
379 return e ;
380}
381
382bool GStore::FileStore::FileOp::rename( const G::Path & src , const G::Path & dst )
383{
384 FileWriter claim_writer ;
385 errno_() = 0 ;
386 bool ok = G::File::rename( src , dst , std::nothrow ) ;
387 errno_() = G::Process::errno_() ;
388 return ok ;
389}
390
391bool GStore::FileStore::FileOp::renameOnto( const G::Path & src , const G::Path & dst )
392{
393 FileWriter claim_writer ;
394 errno_() = 0 ;
395 bool ok = G::File::renameOnto( src , dst , std::nothrow ) ;
396 errno_() = G::Process::errno_() ;
397 return ok ;
398}
399
400bool GStore::FileStore::FileOp::remove( const G::Path & path ) noexcept
401{
402 try
403 {
404 FileWriter claim_writer ;
405 errno_() = 0 ;
406 bool ok = G::File::remove( path , std::nothrow ) ;
407 errno_() = G::Process::errno_() ;
408 return ok ;
409 }
410 catch(...)
411 {
412 return false ;
413 }
414}
415
416bool GStore::FileStore::FileOp::exists( const G::Path & path )
417{
418 FileReader claim_reader ; // moot
419 errno_() = 0 ;
420 bool ok = G::File::exists( path , std::nothrow ) ;
421 errno_() = G::Process::errno_() ;
422 return ok ;
423}
424
425int GStore::FileStore::FileOp::fdopen( const G::Path & path )
426{
427 FileReader claim_reader ;
428 errno_() = 0 ;
429 int fd = G::File::open( path , G::File::InOutAppend::In ) ;
430 errno_() = G::Process::errno_() ;
431 return fd ;
432}
433
434std::ifstream & GStore::FileStore::FileOp::openIn( std::ifstream & stream , const G::Path & path )
435{
436 FileReader claim_reader ;
437 errno_() = 0 ;
438 G::File::open( stream , path ) ;
439 errno_() = G::Process::errno_() ;
440 return stream ;
441}
442
443std::ofstream & GStore::FileStore::FileOp::openOut( std::ofstream & stream , const G::Path & path )
444{
445 FileWriter claim_writer ;
446 errno_() = 0 ;
447 G::File::open( stream , path ) ;
448 errno_() = G::Process::errno_() ;
449 return stream ;
450}
451
452std::ofstream & GStore::FileStore::FileOp::openAppend( std::ofstream & stream , const G::Path & path )
453{
454 FileWriter claim_writer ;
455 errno_() = 0 ;
456 G::File::open( stream , path , G::File::Append() ) ;
457 errno_() = G::Process::errno_() ;
458 return stream ;
459}
460
461bool GStore::FileStore::FileOp::hardlink( const G::Path & src , const G::Path & dst )
462{
463 FileWriter claim_writer ;
464 errno_() = 0 ;
465 bool copied = false ;
466 bool linked = G::File::hardlink( src , dst , std::nothrow ) ;
467 if( !linked )
468 copied = G::File::copy( src , dst , std::nothrow ) ;
469 errno_() = G::Process::errno_() ;
470
471 // fix up group ownership if hard-linked into a set-group-id directory
472 if( linked )
473 {
474 auto dir_stat = G::File::stat( dst.simple() ? G::Path(".") : dst.dirname() ) ;
475 if( !dir_stat.error && !dir_stat.is_link && dir_stat.inherit )
476 G::File::chgrp( dst , dir_stat.gid , std::nothrow ) ;
477 }
478
479 return linked || copied ;
480}
481
482bool GStore::FileStore::FileOp::copy( const G::Path & src , const G::Path & dst , bool use_hardlink )
483{
484 if( use_hardlink )
485 return hardlink( src , dst ) ;
486 else
487 return copy( src , dst ) ;
488}
489
490bool GStore::FileStore::FileOp::copy( const G::Path & src , const G::Path & dst )
491{
492 FileWriter claim_writer ;
493 errno_() = 0 ;
494 bool ok = G::File::copy( src , dst , std::nothrow ) ;
495 errno_() = G::Process::errno_() ;
496 return ok ;
497}
498
499bool GStore::FileStore::FileOp::mkdir( const G::Path & dir )
500{
501 FileWriter claim_root ;
502 errno_() = 0 ;
503 bool ok = G::File::mkdir( dir , std::nothrow ) ;
504 errno_() = G::Process::errno_() ;
505 return ok ;
506}
507
508bool GStore::FileStore::FileOp::isdir( const G::Path & a , const G::Path & b , const G::Path & c )
509{
510 FileReader claim_reader ;
511 return
512 G::File::isDirectory(a,std::nothrow) &&
513 ( b.empty() || G::File::isDirectory(b,std::nothrow) ) &&
514 ( c.empty() || G::File::isDirectory(c,std::nothrow) ) ;
515}
516
Used by GStore::FileStore, GStore::NewFile and GStore::StoredFile to claim read permissions for readi...
Definition: gfilestore.h:208
DirectoryReader()
Default constructor.
~DirectoryReader()
Destructor. Switches identity back.
A structure containing the contents of an envelope file, with support for file reading,...
Definition: genvelope.h:41
static void read(std::istream &, Envelope &)
Reads an envelope from a stream.
Definition: genvelope.cpp:113
A GStore::MessageStore::Iterator for GStore::FileStore.
Definition: gfilestore.cpp:44
FileReader()
Default constructor.
~FileReader()
Destructor. Switches identity back.
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:56
G::Path deliveryDir() const
Returns the base directory for local delivery.
Definition: gfilestore.cpp:124
static std::string x()
Returns the prefix for envelope header lines.
Definition: gfilestore.cpp:129
FileStore(const G::Path &spool_dir, const G::Path &delivery_dir, const Config &config)
Constructor.
Definition: gfilestore.cpp:109
G::Path directory() const
Returns the spool directory path, as passed in to the constructor.
Definition: gfilestore.cpp:119
MessageId newId()
Hands out a new unique message id.
Definition: gfilestore.cpp:222
G::Path envelopePath(const MessageId &id, State=State::Normal) const
Returns the path for an envelope file.
Definition: gfilestore.cpp:210
static std::unique_ptr< std::ofstream > stream(const G::Path &path)
Opens an output stream to a message file using the appropriate effective userid and umask.
Definition: gfilestore.cpp:198
static Envelope readEnvelope(const G::Path &, std::ifstream *=nullptr)
Used by FileStore sibling classes to read an envelope file.
Definition: gfilestore.cpp:308
static std::string format(int generation=0)
Returns an identifier for the storage format implemented by this class, or some older generation of i...
Definition: gfilestore.cpp:134
G::Path contentPath(const MessageId &id) const
Returns the path for a content file.
Definition: gfilestore.cpp:205
static bool knownFormat(const std::string &format)
Returns true if the storage format string is recognised and supported for reading.
Definition: gfilestore.cpp:151
Used by GStore::FileStore, GStore::NewFile and GStore::StoredFile to claim write permissions.
Definition: gfilestore.h:230
FileWriter()
Default constructor.
Definition: gfilestore.cpp:365
~FileWriter()
Destructor. Switches identity back.
A somewhat opaque identifer for a GStore::MessageStore message id.
Definition: gmessagestore.h:43
A iterator similar to G::DirectoryIterator but doing all file i/o in one go and providing a sorted re...
Definition: gdirectory.h:150
Path filePath() const
Returns the current path.
Definition: gdirectory.cpp:158
bool more()
Returns true if more and advances by one.
Definition: gdirectory.cpp:130
std::size_t readType(const Path &dir, std::string_view suffix, unsigned int limit=0U)
An initialiser that is to be used after default construction.
Definition: gdirectory.cpp:94
An encapsulation of a file system directory that works with G::DirectoryIterator.
Definition: gdirectory.h:48
static std::string tmp()
A convenience function for constructing a filename for writeable().
Definition: gdirectory.cpp:55
An overload discriminator for G::File::open().
Definition: gfile.h:62
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
Definition: gfile_unix.cpp:56
static bool isDirectory(const Path &path, std::nothrow_t)
Returns true if the path exists() and is a directory.
Definition: gfile.cpp:179
static bool rename(const Path &from, const Path &to, std::nothrow_t) noexcept
Renames the file.
Definition: gfile.cpp:40
static bool remove(const Path &path, std::nothrow_t) noexcept
Deletes the file or directory. Returns false on error.
Definition: gfile_unix.cpp:177
static Stat stat(const Path &path, bool symlink_nofollow=false)
Returns a file status structure.
Definition: gfile.cpp:174
static bool exists(const Path &file)
Returns true if the file (directory, device etc.) exists.
Definition: gfile.cpp:136
static bool renameOnto(const Path &from, const Path &to, std::nothrow_t) noexcept
Renames the file, deleting 'to' first if necessary.
Definition: gfile_unix.cpp:144
static bool copy(const Path &from, const Path &to, std::nothrow_t)
Copies a file. Returns false on error.
Definition: gfile.cpp:67
static bool mkdir(const Path &dir, std::nothrow_t)
Creates a directory.
Definition: gfile.cpp:235
static bool hardlink(const Path &src, const Path &dst, std::nothrow_t)
Creates a hard link.
Definition: gfile_unix.cpp:390
static void chgrp(const Path &file, const std::string &group)
Sets the file group ownership. Throws on error.
Definition: gfile_unix.cpp:370
A Path object represents a file system path.
Definition: gpath.h:82
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:339
Path withoutExtension() const
Returns a path without the basename extension, if any.
Definition: gpath.cpp:357
Path dirname() const
Returns the path without the rightmost part, ignoring "." parts.
Definition: gpath.cpp:347
bool simple() const
Returns true if the path has a single component (ignoring "." parts), ie.
Definition: gpath.cpp:319
Path withExtension(const std::string &ext) const
Returns the path with the new basename extension.
Definition: gpath.cpp:375
std::string str() const
Returns the path string.
Definition: gpath.h:243
bool empty() const noexcept
Returns true if the path is empty.
Definition: gpath.h:237
Process-id class.
Definition: gprocess.h:167
static std::string strerror(int errno_)
Translates an 'errno' value into a meaningful diagnostic string.
static int errno_(const SignalSafe &=G::SignalSafe()) noexcept
Returns the process's current 'errno' value.
static SystemTime now()
Factory function for the current time.
Definition: gdatetime.cpp:328
std::time_t s() const noexcept
Returns the number of seconds since the start of the epoch.
Definition: gdatetime.cpp:380
A simple version of boost::format for formatting strings in an i18n-friendly way.
Definition: gformat.h:46
Message store classes.
Definition: genvelope.cpp:30
Low-level classes.
Definition: garg.h:36
const char * txt(const char *p) noexcept
A briefer alternative to G::gettext().
Definition: ggettext.h:74
Configuration structure for GStore::FileStore.
Definition: gfilestore.h:69
A base class for GStore::MessageStore iterators.
Definition: gmessagestore.h:97
Information on the SMTP options used when submitted.
Definition: gmessagestore.h:84
A slot holder, with connect() and emit() methods.
Definition: gslot.h:184