E-MailRelay

gfile.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 gfile.cpp
19///
20
21#include "gdef.h"
22#include "glimits.h"
23#include "gfile.h"
24#include "gprocess.h"
25#include "gdate.h"
26#include "gtime.h"
27#include "gdatetime.h"
28#include "glog.h"
29#include <iostream>
30#include <cstdio>
31
32 bool G::File::renameImp( const char * from , const char * to , int * e ) noexcept
33{
34 bool ok = from && to && 0 == std::rename( from , to ) ;
35 if( e )
36 *e = ok ? 0 : ( (from && to) ? Process::errno_() : EINVAL ) ;
37 return ok ;
38}
39
40 bool G::File::rename( const Path & from , const Path & to , std::nothrow_t ) noexcept
41{
42 static_assert( noexcept(from.cstr()) , "" ) ;
43 return renameImp( from.cstr() , to.cstr() , nullptr ) ;
44}
45
46 void G::File::rename( const Path & from , const Path & to , bool ignore_missing )
47{
48 int e = 0 ;
49 bool ok = renameImp( from.cstr() , to.cstr() , &e ) ;
50 bool is_missing = !ok && e == ENOENT ;
51 if( !ok && !(is_missing && ignore_missing) )
52 {
53 throw CannotRename( std::string() + "[" + from.str() + "] to [" + to.str() + "]" ) ;
54 }
55 G_DEBUG( "G::File::rename: \"" << from << "\" -> \"" << to << "\": success=" << ok ) ;
56}
57
58#ifndef G_LIB_SMALL
59 void G::File::copy( const Path & from , const Path & to )
60{
61 std::string reason = copy( from , to , 0 ) ;
62 if( !reason.empty() )
63 throw CannotCopy( std::string() + "[" + from.str() + "] to [" + to.str() + "]: " + reason ) ;
64}
65#endif
66
67 bool G::File::copy( const Path & from , const Path & to , std::nothrow_t )
68{
69 return copy(from,to,0).empty() ;
70}
71
72#ifndef G_LIB_SMALL
73 bool G::File::copyInto( const Path & from , const Path & to_dir , std::nothrow_t )
74{
75 G::Path to = to_dir / from.basename() ;
76 bool ok = copy(from,to,0).empty() ;
77 if( ok && isExecutable(from,std::nothrow) )
78 ok = chmodx( to , std::nothrow ) ;
79 return ok ;
80}
81#endif
82
83std::string G::File::copy( const Path & from , const Path & to , int )
84{
85 std::ifstream in ; open( in , from ) ;
86 if( !in.good() )
87 return "cannot open input file" ;
88
89 std::ofstream out ; open( out , to ) ;
90 if( !out.good() )
91 return "cannot open output file" ;
92
93 out << in.rdbuf() ;
94
95 if( in.fail() )
96 return "read error" ;
97
98 //bool empty = in.tellg() == std::streampos(0) ; // not uclibc++
99 bool empty = false ;
100
101 in.close() ;
102 out.close() ;
103
104 if( out.fail() && !empty )
105 return "write error" ;
106
107 return {} ;
108}
109
110 void G::File::copy( std::istream & in , std::ostream & out , std::streamsize limit , std::size_t block )
111{
112 std::ios_base::iostate in_state = in.rdstate() ;
113
114 block = block ? block : static_cast<std::string::size_type>(Limits<>::file_buffer) ;
115 std::vector<char> buffer( block ) ;
116
117 const auto b = static_cast<std::streamsize>(block) ;
118 std::streamsize size = 0U ;
119 while( ( limit == 0U || size < limit ) && in.good() && out.good() )
120 {
121 std::streamsize request = limit == 0U || (limit-size) > b ? b : (limit-size) ;
122 in.read( buffer.data() , request ) ;
123 std::streamsize result = in.gcount() ;
124 if( result == 0U )
125 break ;
126 out.write( buffer.data() , result ) ;
127 size += result ;
128 }
129
130 out.flush() ;
131
132 // restore the input failbit because it might have been set by us reading an incomplete block at eof
133 in.clear( (in.rdstate() & ~std::ios_base::failbit) | (in_state & std::ios_base::failbit) ) ;
134}
135
136 bool G::File::exists( const Path & path )
137{
138 return path.empty() ? false : exists( path , false , true ) ;
139}
140
141 bool G::File::exists( const Path & path , std::nothrow_t )
142{
143 return path.empty() ? false : exists( path , false , false ) ;
144}
145
146 bool G::File::exists( const Path & path , bool error_return_value , bool do_throw )
147{
148 bool enoent = false ;
149 bool eaccess = false ;
150 bool rc = existsImp( path.cstr() , enoent , eaccess ) ; // o/s-specific
151 if( !rc && enoent )
152 {
153 return false ;
154 }
155 else if( !rc && do_throw )
156 {
157 throw StatError( path.str() , eaccess?"permission denied":"" ) ;
158 }
159 else if( !rc )
160 {
161 return error_return_value ;
162 }
163 return true ;
164}
165
166#ifndef G_LIB_SMALL
167 bool G::File::isLink( const Path & path , std::nothrow_t )
168{
169 Stat s = statImp( path.cstr() , /*symlink_nofollow=*/true ) ;
170 return 0 == s.error && s.is_link ;
171}
172#endif
173
174 G::File::Stat G::File::stat( const Path & path , bool symlink_nofollow )
175{
176 return statImp( path.cstr() , symlink_nofollow ) ;
177}
178
179 bool G::File::isDirectory( const Path & path , std::nothrow_t )
180{
181 Stat s = statImp( path.cstr() ) ;
182 return 0 == s.error && s.is_dir ;
183}
184
185 bool G::File::isExecutable( const Path & path , std::nothrow_t )
186{
187 Stat s = statImp( path.cstr() ) ;
188 return 0 == s.error && s.is_executable ;
189}
190
191#ifndef G_LIB_SMALL
192 bool G::File::isEmpty( const Path & path , std::nothrow_t )
193{
194 Stat s = statImp( path.cstr() ) ;
195 return 0 == s.error && s.is_empty ;
196}
197#endif
198
199std::string G::File::sizeString( const Path & path )
200{
201 Stat s = statImp( path.cstr() ) ;
202 return s.error ? std::string() : std::to_string(s.size) ;
203}
204
205 G::SystemTime G::File::time( const Path & path )
206{
207 Stat s = statImp( path.cstr() ) ;
208 if( s.error )
209 throw TimeError( path.str() , Process::strerror(s.error) ) ;
210 return SystemTime( s.mtime_s , s.mtime_us ) ;
211}
212
213#ifndef G_LIB_SMALL
214 G::SystemTime G::File::time( const Path & path , std::nothrow_t )
215{
216 Stat s = statImp( path.cstr() ) ;
217 if( s.error )
218 return SystemTime( 0 ) ;
219 return SystemTime( s.mtime_s , s.mtime_us ) ;
220}
221#endif
222
223 bool G::File::chmodx( const Path & path , std::nothrow_t )
224{
225 return chmodx( path , false ) ;
226}
227
228#ifndef G_LIB_SMALL
229 void G::File::chmodx( const Path & path )
230{
231 chmodx( path , true ) ;
232}
233#endif
234
235 bool G::File::mkdir( const Path & dir , std::nothrow_t )
236{
237 return 0 == mkdirImp( dir ) ;
238}
239
240#ifndef G_LIB_SMALL
241 void G::File::mkdir( const Path & dir )
242{
243 int e = mkdirImp( dir ) ;
244 if( e )
245 throw CannotMkdir( dir.str() , Process::strerror(e) ) ;
246}
247#endif
248
249 bool G::File::mkdirsImp( const Path & path_in , int & e , int limit )
250{
251 if( path_in.empty() )
252 return true ;
253
254 auto parts = path_in.split() ;
255 Path path ;
256 for( const auto & part : parts )
257 {
258 path.pathAppend( part ) ;
259 if( path.isRoot() )
260 continue ;
261 e = mkdirImp( path ) ;
262 if( e == EEXIST )
263 continue ;
264 else if( e )
265 break ;
266 if( --limit <= 0 )
267 {
268 e = E2BIG ;
269 break ;
270 }
271 }
272 return e == 0 || e == EEXIST ;
273}
274
275#ifndef G_LIB_SMALL
276 bool G::File::mkdirs( const Path & path , std::nothrow_t , int limit )
277{
278 int e = 0 ;
279 return mkdirsImp( path , e , limit ) ;
280}
281#endif
282
283#ifndef G_LIB_SMALL
284 void G::File::mkdirs( const Path & path , int limit )
285{
286 int e = 0 ;
287 if( !mkdirsImp(path,e,limit) )
288 throw CannotMkdir( path.str() , e ? G::Process::strerror(e) : std::string() ) ;
289}
290#endif
291
292#ifndef G_LIB_SMALL
293 int G::File::compare( const Path & path_1 , const Path & path_2 , bool ignore_whitespace )
294{
295 std::ifstream file_1 ; open( file_1 , path_1 ) ;
296 std::ifstream file_2 ; open( file_2 , path_2 ) ;
297 constexpr int eof = std::char_traits<char>::eof() ; // EOF
298 if( !file_1.good() && !file_2.good() ) return -1 ;
299 if( !file_1.good() ) return -1 ;
300 if( !file_2.good() ) return 1 ;
301 int result = 0 ;
302 int a = eof ;
303 int b = eof ;
304 auto isspace = [](int c){ return c == ' ' || c == '\t' || c == '\n' || c == '\r' ; } ;
305 for(;;)
306 {
307 do { a = file_1.get() ; } while( ignore_whitespace && isspace(a) ) ;
308 do { b = file_2.get() ; } while( ignore_whitespace && isspace(b) ) ;
309 if( a == eof && b == eof )
310 break ;
311 if( a != b )
312 {
313 result = a < b ? -1 : 1 ;
314 break ;
315 }
316 }
317 return result ;
318}
319#endif
320
321#ifndef G_LIB_SMALL
322 G::Path G::File::backup( const Path & path , std::nothrow_t )
323{
324 constexpr char prefix = G::is_windows() ? '~' : '.' ;
325 constexpr char sep = '~' ;
326 constexpr unsigned int limit = 100U ;
327 Path backup_path ;
328 for( unsigned int version = 1U ; version <= limit ; version++ )
329 {
330 backup_path = path.dirname() /
331 std::string(1U,prefix).append(path.basename()).append(1U,sep).append(std::to_string(version==limit?1:version)) ;
332 if( !exists( backup_path , std::nothrow ) || version == limit )
333 break ;
334 }
335 Process::Umask umask( Process::Umask::Mode::Tightest ) ;
336 bool copied = File::copy( path , backup_path , std::nothrow ) ;
337 return copied ? backup_path : G::Path() ;
338}
339#endif
340
G::File::isExecutable
static bool isExecutable(const Path &, std::nothrow_t)
Returns true if the path is probably executable by the calling process.
Definition: gfile.cpp:185
G::File::time
static SystemTime time(const Path &file)
Returns the file's timestamp. Throws on error.
Definition: gfile.cpp:205
G::File::isEmpty
static bool isEmpty(const Path &file, std::nothrow_t)
Returns true if the file size is zero.
Definition: gfile.cpp:192
G::File::isDirectory
static bool isDirectory(const Path &path, std::nothrow_t)
Returns true if the path exists() and is a directory.
Definition: gfile.cpp:179
G::File::sizeString
static std::string sizeString(const Path &file)
Returns the file's size in string format.
Definition: gfile.cpp:199
G::File::rename
static bool rename(const Path &from, const Path &to, std::nothrow_t) noexcept
Renames the file.
Definition: gfile.cpp:40
G::File::stat
static Stat stat(const Path &path, bool symlink_nofollow=false)
Returns a file status structure.
Definition: gfile.cpp:174
G::File::exists
static bool exists(const Path &file)
Returns true if the file (directory, device etc.) exists.
Definition: gfile.cpp:136
G::File::chmodx
static void chmodx(const Path &file)
Makes the file executable. Throws on error.
Definition: gfile.cpp:229
G::File::isLink
static bool isLink(const Path &path, std::nothrow_t)
Returns true if the path is an existing symlink.
Definition: gfile.cpp:167
G::File::backup
static Path backup(const Path &from, std::nothrow_t)
Creates a backup copy of the given file in the same directory and with a lightly-mangled filename.
Definition: gfile.cpp:322
G::File::mkdirs
static bool mkdirs(const Path &dir, std::nothrow_t, int=100)
Creates a directory and all necessary parents.
Definition: gfile.cpp:276
G::File::copy
static bool copy(const Path &from, const Path &to, std::nothrow_t)
Copies a file. Returns false on error.
Definition: gfile.cpp:67
G::File::copyInto
static bool copyInto(const Path &from, const Path &to_dir, std::nothrow_t)
Copies a file into a directory and does a chmodx() if necessary.
Definition: gfile.cpp:73
G::File::mkdir
static bool mkdir(const Path &dir, std::nothrow_t)
Creates a directory.
Definition: gfile.cpp:235
G::File::compare
static int compare(const Path &, const Path &, bool ignore_whitespace=false)
Compares the contents of the two files. Returns 0, 1 or -1.
Definition: gfile.cpp:293
G::Path
A Path object represents a file system path.
Definition: gpath.h:82
G::Path::cstr
const value_type * cstr() const noexcept
Returns the path's c-string.
Definition: gpath.h:249
G::Path::isRoot
bool isRoot() const noexcept
Returns true if the path is a root, like "/", "c:", "c:/", "\\server\volume" etc.
Definition: gpath.cpp:324
G::Path::basename
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:339
G::Path::dirname
Path dirname() const
Returns the path without the rightmost part, ignoring "." parts.
Definition: gpath.cpp:347
G::Path::pathAppend
Path & pathAppend(const std::string &tail)
Appends a filename or a relative path to this path.
Definition: gpath.cpp:404
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.h:243
G::Path::split
StringArray split() const
Spits the path into a list of component parts (ignoring "." parts unless the whole path is "....
Definition: gpath.cpp:426
G::Path::empty
bool empty() const noexcept
Returns true if the path is empty.
Definition: gpath.h:237
G::Process::Umask
Used to temporarily modify the process umask.
Definition: gprocess.h:197
G::Process::strerror
static std::string strerror(int errno_)
Translates an 'errno' value into a meaningful diagnostic string.
Definition: gprocess_unix.cpp:142
G::SystemTime
Represents a unix-epoch time with microsecond resolution.
Definition: gdatetime.h:140
gdate.h
gdatetime.h
gfile.h
glimits.h
glog.h
gprocess.h
gtime.h
std
STL namespace.
G::File::Stat
A portable 'struct stat'.
Definition: gfile.h:68
G::Limits
A set of compile-time buffer sizes.
Definition: glimits.h:48