E-MailRelay
gfile_unix.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 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 gfile_unix.cpp
19///
20
21#include "gdef.h"
22#include "gfile.h"
23#include "gstr.h"
24#include "gprocess.h"
25#include "glog.h"
26#include "gassert.h"
27#include <vector>
28#include <sstream>
29#include <cerrno> // ENOENT etc
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <unistd.h>
33#include <fcntl.h>
34
35namespace G
36{
37 namespace FileImp
38 {
39 std::pair<bool,mode_t> newmode( mode_t , const std::string & ) ;
40 std::pair<std::time_t,unsigned int> mtime( struct stat & statbuf ) noexcept
41 {
42 #if GCONFIG_HAVE_STATBUF_TIMESPEC
43 return { statbuf.st_mtimespec.tv_sec , statbuf.st_mtimespec.tv_nsec/1000U } ;
44 #else
45 #if GCONFIG_HAVE_STATBUF_NSEC
46 return { statbuf.st_mtime , statbuf.st_mtim.tv_nsec/1000U } ;
47 #else
48 return { statbuf.st_mtime , 0U } ;
49 #endif
50 #endif
51 }
52 }
53}
54
55void G::File::open( std::ofstream & ofstream , const Path & path )
56{
57 ofstream.open( path.cstr() , std::ios_base::out | std::ios_base::binary ) ;
58}
59
60void G::File::open( std::ofstream & ofstream , const Path & path , Text )
61{
62 ofstream.open( path.cstr() , std::ios_base::out ) ;
63}
64
65void G::File::open( std::ofstream & ofstream , const Path & path , Append )
66{
67 ofstream.open( path.cstr() , std::ios_base::app | std::ios_base::binary ) ;
68}
69
70void G::File::open( std::ifstream & ifstream , const Path & path )
71{
72 ifstream.open( path.cstr() , std::ios_base::in | std::ios_base::binary ) ;
73}
74
75#ifndef G_LIB_SMALL
76void G::File::open( std::ifstream & ifstream , const Path & path , Text )
77{
78 ifstream.open( path.cstr() , std::ios_base::in ) ;
79}
80#endif
81
82std::filebuf * G::File::open( std::filebuf & fb , const Path & path , InOut inout )
83{
84 return
85 inout == InOut::In ?
86 fb.open( path.cstr() , std::ios_base::in | std::ios_base::binary ) :
87 fb.open( path.cstr() , std::ios_base::out | std::ios_base::binary ) ;
88}
89
90int G::File::open( const char * path , InOutAppend mode ) noexcept
91{
92 if( mode == InOutAppend::In )
93 return ::open( path , O_RDONLY ) ; // NOLINT
94 else if( mode == InOutAppend::Out )
95 return ::open( path , O_WRONLY|O_CREAT|O_TRUNC , 0666 ) ; // NOLINT
96 else
97 return ::open( path , O_WRONLY|O_CREAT|O_APPEND , 0666 ) ; // NOLINT
98}
99
100#ifndef G_LIB_SMALL
101int G::File::open( const char * path , CreateExclusive ) noexcept
102{
103 return ::open( path , O_WRONLY|O_CREAT|O_EXCL , 0666 ) ; // NOLINT
104}
105#endif
106
107#ifndef G_LIB_SMALL
108std::FILE * G::File::fopen( const char * path , const char * mode ) noexcept
109{
110 return std::fopen( path , mode ) ;
111}
112#endif
113
114bool G::File::probe( const char * path ) noexcept
115{
116 int fd = ::open( path , O_WRONLY|O_CREAT|O_EXCL , 0666 ) ; // NOLINT
117 if( fd < 0 )
118 return false ;
119 std::remove( path ) ;
120 ::close( fd ) ;
121 return true ;
122}
123
124#ifndef G_LIB_SMALL
125void G::File::create( const Path & path )
126{
127 int fd = ::open( path.cstr() , O_RDONLY|O_CREAT , 0666 ) ; // NOLINT
128 if( fd < 0 )
129 throw CannotCreate( path.str() ) ;
130 ::close( fd ) ;
131}
132#endif
133
134bool G::File::renameOnto( const Path & from , const Path & to , std::nothrow_t ) noexcept
135{
136 return 0 == std::rename( from.cstr() , to.cstr() ) ; // overwrites 'to'
137}
138
139ssize_t G::File::read( int fd , char * p , std::size_t n ) noexcept
140{
141 return ::read( fd , p , n ) ;
142}
143
144ssize_t G::File::write( int fd , const char * p , std::size_t n ) noexcept
145{
146 return ::write( fd , p , n ) ;
147}
148
149void G::File::close( int fd ) noexcept
150{
151 ::close( fd ) ;
152}
153
154int G::File::mkdirImp( const Path & dir ) noexcept
155{
156 int rc = ::mkdir( dir.cstr() , 0777 ) ; // open permissions, but limited by umask
157 if( rc == 0 )
158 {
159 return 0 ;
160 }
161 else
162 {
163 int e = G::Process::errno_() ;
164 if( e == 0 ) e = EINVAL ;
165 return e ;
166 }
167}
168
169G::File::Stat G::File::statImp( const char * path , bool read_symlink ) noexcept
170{
171 Stat s ;
172 struct stat statbuf {} ;
173 if( 0 == ( read_symlink ? (::lstat(path,&statbuf)) : (::stat(path,&statbuf)) ) )
174 {
175 s.error = 0 ;
176 s.enoent = false ;
177 s.eaccess = false ;
178 s.is_link = (statbuf.st_mode & S_IFMT) == S_IFLNK ; // NOLINT
179 s.is_dir = (statbuf.st_mode & S_IFMT) == S_IFDIR ; // NOLINT
180 s.is_executable = !!(statbuf.st_mode & S_IXUSR) && !!(statbuf.st_mode & S_IRUSR) ; // indicitive // NOLINT
181 s.is_empty = statbuf.st_size == 0 ;
182 s.mtime_s = FileImp::mtime(statbuf).first ;
183 s.mtime_us = FileImp::mtime(statbuf).second ;
184 s.mode = static_cast<unsigned long>( statbuf.st_mode & mode_t(07777) ) ; // NOLINT
185 s.size = static_cast<unsigned long long>( statbuf.st_size ) ;
186 s.blocks = static_cast<unsigned long long>(statbuf.st_size) >> 24U ;
187 s.uid = statbuf.st_uid ;
188 s.gid = statbuf.st_gid ;
189 s.inherit = s.is_dir && ( G::is_bsd() || ( statbuf.st_mode & S_ISGID ) ) ;
190 }
191 else
192 {
193 int error = Process::errno_() ;
194 s.error = error ? error : EINVAL ;
195 s.enoent = error == ENOENT || error == ENOTDIR ;
196 s.eaccess = error == EACCES ;
197 }
198 return s ;
199}
200
201bool G::File::existsImp( const char * path , bool & enoent , bool & eaccess ) noexcept
202{
203 Stat s = statImp( path ) ;
204 if( s.error )
205 {
206 enoent = s.enoent ;
207 eaccess = s.eaccess ;
208 }
209 return s.error == 0 ;
210}
211
212bool G::File::chmodx( const Path & path , bool do_throw )
213{
214 Stat s = statImp( path.cstr() ) ;
215 mode_t mode = s.error ? mode_t(0777) : mode_t(s.mode) ;
216
217 mode |= ( S_IRUSR | S_IXUSR ) ; // add user-read and user-executable // NOLINT
218 if( mode & S_IRGRP ) mode |= S_IXGRP ; // add group-executable iff group-read // NOLINT
219 if( mode & S_IROTH ) mode |= S_IXOTH ; // add world-executable iff world-read // NOLINT
220
221 // apply the current umask
222 mode_t mask = ::umask( 0 ) ; ::umask( mask ) ;
223 mode &= ~mask ;
224
225 bool ok = 0 == ::chmod( path.cstr() , mode ) ;
226 if( !ok && do_throw )
227 throw CannotChmod( path.str() ) ;
228 return ok ;
229}
230
231#ifndef G_LIB_SMALL
232void G::File::chmod( const Path & path , const std::string & spec )
233{
234 if( !chmod( path , spec , std::nothrow ) )
235 throw CannotChmod( path.str() ) ;
236}
237#endif
238
239bool G::File::chmod( const Path & path , const std::string & spec , std::nothrow_t )
240{
241 if( spec.empty() )
242 {
243 return false ;
244 }
245 else if( spec.find_first_not_of("01234567") == std::string::npos )
246 {
247 mode_t mode = static_cast<mode_t>( strtoul( spec.c_str() , nullptr , 8 ) ) ;
248 return mode <= 07777 && 0 == ::chmod( path.cstr() , mode ) ;
249 }
250 else
251 {
252 Stat s = statImp( path.cstr() ) ;
253 if( s.error )
254 return false ;
255 std::pair<bool,mode_t> pair = FileImp::newmode( s.mode , spec ) ;
256 return pair.first && 0 == ::chmod( path.cstr() , pair.second ) ;
257 }
258}
259
260std::pair<bool,mode_t> G::FileImp::newmode( mode_t mode , const std::string & spec_in )
261{
262 mode &= mode_t(07777) ;
263 G::StringArray spec_list = G::Str::splitIntoFields( spec_in , ',' ) ;
264 bool ok = !spec_list.empty() ;
265 for( auto spec : spec_list )
266 {
267 if( spec.size() >= 2U &&
268 ( spec.at(0U) == '+' || spec.at(0U) == '-' || spec.at(0U) == '=' ) )
269 {
270 spec.insert( 0U , "a" ) ;
271 }
272 if( spec.size() >= 3U &&
273 ( spec.at(0U) == 'u' || spec.at(0U) == 'g' || spec.at(0U) == 'o' || spec.at(0U) == 'a' ) &&
274 ( spec.at(1U) == '+' || spec.at(1U) == '-' || spec.at(1U) == '=' ) )
275 {
276 mode_t part = 0 ;
277 mode_t special = 0 ;
278 for( const char * p = spec.c_str()+2 ; *p ; p++ )
279 {
280 if( *p == 'r' )
281 part |= mode_t(4) ;
282 else if( *p == 'w' )
283 part |= mode_t(2) ;
284 else if( *p == 'x' )
285 part |= mode_t(1) ;
286 else if( *p == 's' && spec[0] == 'u' )
287 special |= S_ISUID ; // NOLINT
288 else if( *p == 's' && spec[0] == 'g' )
289 special |= S_ISGID ; // NOLINT
290 else if( *p == 't' && spec[0] == 'o' )
291 special |= S_ISVTX ; // NOLINT
292 else
293 ok = false ;
294 }
295 unsigned int shift = spec[0]=='u' ? 6U : (spec[0]=='g'?3U:0U) ;
296 if( spec[0] == 'a' )
297 {
298 mode_t mask = umask(0) ; umask( mask ) ;
299 part = ( ((part<<6U)|(part<<3U)|part) & ~mask ) ;
300 }
301 if( spec[1] == '=' && spec[0] == 'a' )
302 {
303 mode = part ;
304 }
305 else if( spec[1] == '=' )
306 {
307 mode_t clearbits = (mode_t(7)<<shift) | (spec[0]=='u'?mode_t(S_ISUID):(spec[0]=='g'?mode_t(S_ISGID):mode_t(S_ISVTX))) ;
308 mode &= ~clearbits ;
309 mode |= (part<<shift) ;
310 mode |= special ;
311 }
312 else if( spec[1] == '+' )
313 {
314 mode |= ( (part<<shift) | special ) ;
315 }
316 else
317 {
318 mode &= ~( (part<<shift) | special ) ;
319 }
320 }
321 else
322 {
323 ok = false ;
324 }
325 }
326 return { ok , mode } ;
327}
328
329#ifndef G_LIB_SMALL
330void G::File::chgrp( const Path & path , const std::string & group )
331{
332 bool ok = 0 == ::chown( path.cstr() , -1 , Identity::lookupGroup(group) ) ;
333 if( !ok )
334 throw CannotChgrp( path.str() ) ;
335}
336#endif
337
338#ifndef G_LIB_SMALL
339bool G::File::chgrp( const Path & path , const std::string & group , std::nothrow_t )
340{
341 return 0 == ::chown( path.cstr() , -1 , Identity::lookupGroup(group) ) ;
342}
343#endif
344
345bool G::File::chgrp( const Path & path , gid_t group_id , std::nothrow_t )
346{
347 return 0 == ::chown( path.cstr() , -1 , group_id ) ;
348}
349
350bool G::File::hardlink( const Path & src , const Path & dst , std::nothrow_t )
351{
352 return 0 == ::link( src.cstr() , dst.cstr() ) ;
353}
354
355#ifndef G_LIB_SMALL
356void G::File::link( const Path & target , const Path & new_link )
357{
358 if( linked(target,new_link) ) // optimisation
359 return ;
360
361 if( exists(new_link) )
362 remove( new_link , std::nothrow ) ;
363
364 int error = linkImp( target.cstr() , new_link.cstr() ) ;
365
366 if( error != 0 )
367 {
368 std::ostringstream ss ;
369 ss << "[" << new_link << "] -> [" << target << "] " "(" << error << ")" ;
370 throw CannotLink( ss.str() ) ;
371 }
372}
373#endif
374
375#ifndef G_LIB_SMALL
376bool G::File::link( const Path & target , const Path & new_link , std::nothrow_t )
377{
378 if( linked(target,new_link) ) // optimisation
379 return true ;
380
381 if( exists(new_link) )
382 remove( new_link , std::nothrow ) ;
383
384 return 0 == linkImp( target.cstr() , new_link.cstr() ) ;
385}
386#endif
387
388int G::File::linkImp( const char * target , const char * new_link )
389{
390 int rc = ::symlink( target , new_link ) ;
391 int error = Process::errno_() ;
392 return rc == 0 ? 0 : (error?error:EINVAL) ;
393}
394
395#ifndef G_LIB_SMALL
397{
398 Path result = readlink( link , std::nothrow ) ;
399 if( result.empty() )
400 throw CannotReadLink( link.str() ) ;
401 return result ;
402}
403#endif
404
405G::Path G::File::readlink( const Path & link , std::nothrow_t )
406{
407 Path result ;
408 struct stat statbuf {} ;
409 int rc = ::lstat( link.cstr() , &statbuf ) ;
410 if( rc == 0 )
411 {
412 std::size_t buffer_size = statbuf.st_size ? (statbuf.st_size+1U) : 1024U ;
413 std::vector<char> buffer( buffer_size , '\0' ) ;
414 ssize_t nread = ::readlink( link.cstr() , &buffer[0] , buffer.size() ) ;
415
416 // (filesystem race can cause truncation -- treat as an error)
417 if( nread > 0 && static_cast<std::size_t>(nread) < buffer.size() )
418 {
419 G_ASSERT( buffer.at(static_cast<std::size_t>(nread-1)) != '\0' ) ; // readlink does not null-terminate
420 result = Path( std::string( &buffer[0] , static_cast<std::size_t>(nread) ) ) ;
421 }
422 }
423 return result ;
424}
425
426bool G::File::linked( const Path & target , const Path & new_link )
427{
428 // see if already linked correctly - errors and overflows are not fatal
429 return readlink(new_link,std::nothrow) == target ;
430}
431
432std::streamoff G::File::seek( int fd , std::streamoff offset , Seek origin ) noexcept
433{
434 off_t rc = ::lseek( fd , static_cast<off_t>(offset) ,
435 origin == Seek::Start ? SEEK_SET : ( origin == Seek::End ? SEEK_END : SEEK_CUR ) ) ;
436 return static_cast<std::streamoff>(rc) ;
437}
438
439#ifndef G_LIB_SMALL
440void G::File::setNonBlocking( int fd ) noexcept
441{
442 int flags = ::fcntl( fd , F_GETFL ) ; // NOLINT
443 flags |= O_NONBLOCK ; // NOLINT
444 GDEF_IGNORE_RETURN ::fcntl( fd , F_SETFL , flags ) ; // NOLINT
445}
446#endif
447
An overload discriminator for G::File::open().
Definition: gfile.h:61
An overload discriminator for G::File::open().
Definition: gfile.h:63
static std::FILE * fopen(const char *, const char *mode) noexcept
Calls std::fopen().
Definition: gfile_unix.cpp:108
static bool probe(const char *) noexcept
Creates and deletes a temporary probe file.
Definition: gfile_unix.cpp:114
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
Definition: gfile_unix.cpp:55
static void close(int fd) noexcept
Calls ::close() or equivalent.
Definition: gfile_unix.cpp:149
static void link(const Path &target, const Path &new_link)
Creates a symlink.
Definition: gfile_unix.cpp:356
static std::streamoff seek(int fd, std::streamoff offset, Seek) noexcept
Does ::lseek() or equivalent.
Definition: gfile_unix.cpp:432
static void setNonBlocking(int fd) noexcept
Sets the file descriptor to non-blocking mode.
Definition: gfile_unix.cpp:440
static ssize_t write(int fd, const char *, std::size_t) noexcept
Calls ::write() or equivalent.
Definition: gfile_unix.cpp:144
static void chmod(const Path &file, const std::string &spec)
Sets the file permissions.
Definition: gfile_unix.cpp:232
static void chmodx(const Path &file)
Makes the file executable. Throws on error.
Definition: gfile.cpp:239
static ssize_t read(int fd, char *, std::size_t) noexcept
Calls ::read() or equivalent.
Definition: gfile_unix.cpp:139
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:134
static void create(const Path &)
Creates the file if it does not exist.
Definition: gfile_unix.cpp:125
static G::Path readlink(const Path &link)
Reads a symlink. Throws on error.
Definition: gfile_unix.cpp:396
static bool hardlink(const Path &src, const Path &dst, std::nothrow_t)
Creates a hard link.
Definition: gfile_unix.cpp:350
static void chgrp(const Path &file, const std::string &group)
Sets the file group ownership. Throws on error.
Definition: gfile_unix.cpp:330
static gid_t lookupGroup(const std::string &group)
Does a groupname lookup.
A Path object represents a file system path.
Definition: gpath.h:73
const char * cstr() const noexcept
Returns the path string.
Definition: gpath.h:230
std::string str() const
Returns the path string.
Definition: gpath.h:224
bool empty() const noexcept
Returns true if size() is zero.
Definition: gpath.h:212
static int errno_(const SignalSafe &=G::SignalSafe()) noexcept
Returns the process's current 'errno' value.
static void splitIntoFields(string_view in, StringArray &out, char sep, char escape='\0', bool remove_escapes=true)
Splits the string into fields.
Definition: gstr.cpp:1194
Low-level classes.
Definition: garg.h:30
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
An overload discriminator for G::File::open().
Definition: gfile.h:65
A portable 'struct stat'.
Definition: gfile.h:67