E-MailRelay

gfile_unix.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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
35 namespace 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
55 void 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
60 void G::File::open( std::ofstream & ofstream , const Path & path , Text )
61{
62 ofstream.open( path.cstr() , std::ios_base::out ) ;
63}
64
65 void 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
70 void 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 void G::File::open( std::ifstream & ifstream , const Path & path , Text )
76{
77 ifstream.open( path.cstr() , std::ios_base::in ) ;
78}
79
80std::filebuf * G::File::open( std::filebuf & fb , const Path & path , InOut inout )
81{
82 return
83 inout == InOut::In ?
84 fb.open( path.cstr() , std::ios_base::in | std::ios_base::binary ) :
85 fb.open( path.cstr() , std::ios_base::out | std::ios_base::binary ) ;
86}
87
88 int G::File::open( const char * path , InOutAppend mode ) noexcept
89{
90 if( mode == InOutAppend::In )
91 return ::open( path , O_RDONLY ) ; // NOLINT
92 else if( mode == InOutAppend::Out )
93 return ::open( path , O_WRONLY|O_CREAT|O_TRUNC , 0666 ) ; // NOLINT
94 else
95 return ::open( path , O_WRONLY|O_CREAT|O_APPEND , 0666 ) ; // NOLINT
96}
97
98 bool G::File::probe( const char * path ) noexcept
99{
100 int fd = ::open( path , O_WRONLY|O_CREAT|O_EXCL , 0666 ) ; // NOLINT
101 if( fd < 0 )
102 return false ;
103 std::remove( path ) ;
104 ::close( fd ) ;
105 return true ;
106}
107
108 void G::File::create( const Path & path )
109{
110 int fd = ::open( path.cstr() , O_RDONLY|O_CREAT , 0666 ) ; // NOLINT
111 if( fd < 0 )
112 throw CannotCreate( path.str() ) ;
113 ::close( fd ) ;
114}
115
116ssize_t G::File::read( int fd , char * p , std::size_t n ) noexcept
117{
118 return ::read( fd , p , n ) ;
119}
120
121ssize_t G::File::write( int fd , const char * p , std::size_t n ) noexcept
122{
123 return ::write( fd , p , n ) ;
124}
125
126 void G::File::close( int fd ) noexcept
127{
128 ::close( fd ) ;
129}
130
131 int G::File::mkdirImp( const Path & dir ) noexcept
132{
133 int rc = ::mkdir( dir.cstr() , 0777 ) ; // open permissions, but limited by umask
134 if( rc == 0 )
135 {
136 return 0 ;
137 }
138 else
139 {
140 int e = G::Process::errno_() ;
141 if( e == 0 ) e = EINVAL ;
142 return e ;
143 }
144}
145
146 G::File::Stat G::File::statImp( const char * path , bool link ) noexcept
147{
148 Stat s ;
149 struct stat statbuf {} ;
150 if( 0 == ( link ? (::lstat(path,&statbuf)) : (::stat(path,&statbuf)) ) )
151 {
152 s.error = 0 ;
153 s.enoent = false ;
154 s.eaccess = false ;
155 s.is_link = (statbuf.st_mode & S_IFMT) == S_IFLNK ; // NOLINT
156 s.is_dir = (statbuf.st_mode & S_IFMT) == S_IFDIR ; // NOLINT
157 s.is_executable = !!(statbuf.st_mode & S_IXUSR) && !!(statbuf.st_mode & S_IRUSR) ; // indicitive // NOLINT
158 s.is_empty = statbuf.st_size == 0 ;
159 s.mtime_s = FileImp::mtime(statbuf).first ;
160 s.mtime_us = FileImp::mtime(statbuf).second ;
161 s.mode = static_cast<unsigned long>( statbuf.st_mode & mode_t(07777) ) ; // NOLINT
162 s.size = static_cast<unsigned long long>( statbuf.st_size ) ;
163 s.blocks = static_cast<unsigned long long>(statbuf.st_size) >> 24U ;
164 }
165 else
166 {
167 int error = Process::errno_() ;
168 s.error = error ? error : EINVAL ;
169 s.enoent = error == ENOENT || error == ENOTDIR ;
170 s.eaccess = error == EACCES ;
171 }
172 return s ;
173}
174
175 bool G::File::existsImp( const char * path , bool & enoent , bool & eaccess ) noexcept
176{
177 Stat s = statImp( path ) ;
178 if( s.error )
179 {
180 enoent = s.enoent ;
181 eaccess = s.eaccess ;
182 }
183 return s.error == 0 ;
184}
185
186 bool G::File::chmodx( const Path & path , bool do_throw )
187{
188 Stat s = statImp( path.cstr() ) ;
189 mode_t mode = s.error ? mode_t(0777) : mode_t(s.mode) ;
190
191 mode |= ( S_IRUSR | S_IXUSR ) ; // add user-read and user-executable // NOLINT
192 if( mode & S_IRGRP ) mode |= S_IXGRP ; // add group-executable iff group-read // NOLINT
193 if( mode & S_IROTH ) mode |= S_IXOTH ; // add world-executable iff world-read // NOLINT
194
195 // apply the current umask
196 mode_t mask = ::umask( 0 ) ; ::umask( mask ) ;
197 mode &= ~mask ;
198
199 bool ok = 0 == ::chmod( path.cstr() , mode ) ;
200 if( !ok && do_throw )
201 throw CannotChmod( path.str() ) ;
202 return ok ;
203}
204
205 void G::File::chmod( const Path & path , const std::string & spec )
206{
207 if( !chmod( path , spec , std::nothrow ) )
208 throw CannotChmod( path.str() ) ;
209}
210
211 bool G::File::chmod( const Path & path , const std::string & spec , std::nothrow_t )
212{
213 if( spec.empty() )
214 {
215 return false ;
216 }
217 else if( spec.find_first_not_of("01234567") == std::string::npos )
218 {
219 mode_t mode = static_cast<mode_t>( strtoul( spec.c_str() , nullptr , 8 ) ) ;
220 return mode <= 07777 && 0 == ::chmod( path.cstr() , mode ) ;
221 }
222 else
223 {
224 Stat s = statImp( path.cstr() ) ;
225 if( s.error )
226 return false ;
227 std::pair<bool,mode_t> pair = FileImp::newmode( s.mode , spec ) ;
228 return pair.first && 0 == ::chmod( path.cstr() , pair.second ) ;
229 }
230}
231
232std::pair<bool,mode_t> G::FileImp::newmode( mode_t mode , const std::string & spec_in )
233{
234 mode &= mode_t(07777) ;
235 G::StringArray spec_list = G::Str::splitIntoFields( spec_in , "," ) ;
236 bool ok = !spec_list.empty() ;
237 for( auto spec : spec_list )
238 {
239 if( spec.size() >= 2U &&
240 ( spec.at(0U) == '+' || spec.at(0U) == '-' || spec.at(0U) == '=' ) )
241 {
242 spec.insert( 0U , "a" ) ;
243 }
244 if( spec.size() >= 3U &&
245 ( spec.at(0U) == 'u' || spec.at(0U) == 'g' || spec.at(0U) == 'o' || spec.at(0U) == 'a' ) &&
246 ( spec.at(1U) == '+' || spec.at(1U) == '-' || spec.at(1U) == '=' ) )
247 {
248 mode_t part = 0 ;
249 mode_t special = 0 ;
250 for( const char * p = spec.c_str()+2 ; *p ; p++ )
251 {
252 if( *p == 'r' )
253 part |= mode_t(4) ;
254 else if( *p == 'w' )
255 part |= mode_t(2) ;
256 else if( *p == 'x' )
257 part |= mode_t(1) ;
258 else if( *p == 's' && spec[0] == 'u' )
259 special |= S_ISUID ; // NOLINT
260 else if( *p == 's' && spec[0] == 'g' )
261 special |= S_ISGID ; // NOLINT
262 else if( *p == 't' && spec[0] == 'o' )
263 special |= S_ISVTX ; // NOLINT
264 else
265 ok = false ;
266 }
267 unsigned int shift = spec[0]=='u' ? 6U : (spec[0]=='g'?3U:0U) ;
268 if( spec[0] == 'a' )
269 {
270 mode_t mask = umask(0) ; umask( mask ) ;
271 part = ( ((part<<6U)|(part<<3U)|part) & ~mask ) ;
272 }
273 if( spec[1] == '=' && spec[0] == 'a' )
274 {
275 mode = part ;
276 }
277 else if( spec[1] == '=' )
278 {
279 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))) ;
280 mode &= ~clearbits ;
281 mode |= (part<<shift) ;
282 mode |= special ;
283 }
284 else if( spec[1] == '+' )
285 {
286 mode |= ( (part<<shift) | special ) ;
287 }
288 else
289 {
290 mode &= ~( (part<<shift) | special ) ;
291 }
292 }
293 else
294 {
295 ok = false ;
296 }
297 }
298 return { ok , mode } ;
299}
300
301 void G::File::chgrp( const Path & path , const std::string & group )
302{
303 bool ok = 0 == ::chown( path.cstr() , -1 , Identity::lookupGroup(group) ) ;
304 if( !ok )
305 throw CannotChgrp( path.str() ) ;
306}
307
308 bool G::File::chgrp( const Path & path , const std::string & group , std::nothrow_t )
309{
310 return 0 == ::chown( path.cstr() , -1 , Identity::lookupGroup(group) ) ;
311}
312
313 void G::File::link( const Path & target , const Path & new_link )
314{
315 if( linked(target,new_link) ) // optimisation
316 return ;
317
318 if( exists(new_link) )
319 remove( new_link , std::nothrow ) ;
320
321 int error = linkImp( target.cstr() , new_link.cstr() ) ;
322
323 if( error != 0 )
324 {
325 std::ostringstream ss ;
326 ss << "[" << new_link << "] -> [" << target << "] " "(" << error << ")" ;
327 throw CannotLink( ss.str() ) ;
328 }
329}
330
331 bool G::File::link( const Path & target , const Path & new_link , std::nothrow_t )
332{
333 if( linked(target,new_link) ) // optimisation
334 return true ;
335
336 if( exists(new_link) )
337 remove( new_link , std::nothrow ) ;
338
339 return 0 == linkImp( target.cstr() , new_link.cstr() ) ;
340}
341
342 int G::File::linkImp( const char * target , const char * new_link )
343{
344 int rc = ::symlink( target , new_link ) ;
345 int error = Process::errno_() ;
346 return rc == 0 ? 0 : (error?error:EINVAL) ;
347}
348
349 G::Path G::File::readlink( const Path & link )
350{
351 Path result = readlink( link , std::nothrow ) ;
352 if( result.empty() )
353 throw CannotReadLink( link.str() ) ;
354 return result ;
355}
356
357 G::Path G::File::readlink( const Path & link , std::nothrow_t )
358{
359 Path result ;
360 struct stat statbuf {} ;
361 int rc = ::lstat( link.cstr() , &statbuf ) ;
362 if( rc == 0 )
363 {
364 std::size_t buffer_size = statbuf.st_size ? (statbuf.st_size+1U) : 1024U ;
365 std::vector<char> buffer( buffer_size , '\0' ) ;
366 ssize_t nread = ::readlink( link.cstr() , &buffer[0] , buffer.size() ) ;
367
368 // (filesystem race can cause truncation -- treat as an error)
369 if( nread > 0 && static_cast<std::size_t>(nread) < buffer.size() )
370 {
371 G_ASSERT( buffer.at(static_cast<std::size_t>(nread-1)) != '\0' ) ; // readlink does not null-terminate
372 result = Path( std::string( &buffer[0] , static_cast<std::size_t>(nread) ) ) ;
373 }
374 }
375 return result ;
376}
377
378 bool G::File::linked( const Path & target , const Path & new_link )
379{
380 // see if already linked correctly - errors and overflows are not fatal
381 return readlink(new_link,std::nothrow) == target ;
382}
383
G::File::Append
An overload discriminator for G::File::open().
Definition: gfile.h:60
G::File::Text
An overload discriminator for G::File::open().
Definition: gfile.h:62
G::File::probe
static bool probe(const char *) noexcept
Creates and deletes a temporary probe file.
Definition: gfile_unix.cpp:98
G::File::open
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
Definition: gfile_unix.cpp:55
G::File::close
static void close(int fd) noexcept
Calls ::close() or equivalent.
Definition: gfile_unix.cpp:126
G::File::link
static void link(const Path &target, const Path &new_link)
Creates a symlink.
Definition: gfile_unix.cpp:313
G::File::write
static ssize_t write(int fd, const char *, std::size_t) noexcept
Calls ::write() or equivalent.
Definition: gfile_unix.cpp:121
G::File::chmod
static void chmod(const Path &file, const std::string &spec)
Sets the file permissions.
Definition: gfile_unix.cpp:205
G::File::chmodx
static void chmodx(const Path &file)
Makes the file executable. Throws on error.
Definition: gfile.cpp:232
G::File::read
static ssize_t read(int fd, char *, std::size_t) noexcept
Calls ::read() or equivalent.
Definition: gfile_unix.cpp:116
G::File::create
static void create(const Path &)
Creates the file if it does not exist.
Definition: gfile_unix.cpp:108
G::File::readlink
static G::Path readlink(const Path &link)
Reads a symlink. Throws on error.
Definition: gfile_unix.cpp:349
G::File::chgrp
static void chgrp(const Path &file, const std::string &group)
Sets the file group ownership. Throws on error.
Definition: gfile_unix.cpp:301
G::Identity::lookupGroup
static gid_t lookupGroup(const std::string &group)
Does a groupname lookup. Throws on error.
Definition: gidentity_unix.cpp:106
G::Path
A Path object represents a file system path.
Definition: gpath.h:72
G::Path::cstr
const char * cstr() const noexcept
Returns the path string.
Definition: gpath.h:221
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.h:215
G::Path::empty
bool empty() const noexcept
Returns true if size() is zero.
Definition: gpath.h:203
G::Process::errno_
static int errno_(const SignalSafe &=G::SignalSafe()) noexcept
Returns the process's current 'errno' value.
Definition: gprocess_unix.cpp:115
G::Str::splitIntoFields
static void splitIntoFields(const std::string &in, StringArray &out, string_view ws, char escape='\0', bool remove_escapes=true)
Splits the string into fields.
Definition: gstr.cpp:1146
gassert.h
gfile.h
glog.h
gprocess.h
gstr.h
G
Low-level classes.
Definition: galign.h:28
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
G::File::Stat
A portable 'struct stat'.
Definition: gfile.h:64