Merge remote branch 'origin/amavisd-update' into glibc-upgrade
[libi2ncommon] / src / filefunc.cpp
CommitLineData
e93545dd
GE
1/***************************************************************************
2 escape.cpp - escaping of strings
3 -------------------
4 begin : Sun Nov 14 1999
5 copyright : (C) 1999 by Intra2net AG
6 email : info@intra2net.com
7 ***************************************************************************/
8
6a93d84a 9#include <list>
e93545dd 10#include <string>
6a93d84a 11#include <fstream>
0a654ec0 12#include <sstream>
e93545dd 13#include <iostream>
0a654ec0 14#include <stdexcept>
e93545dd
GE
15
16#include <sys/types.h>
17#include <sys/stat.h>
0a654ec0 18#include <dirent.h>
e93545dd
GE
19#include <pwd.h>
20#include <grp.h>
21#include <unistd.h>
0a654ec0 22#include <errno.h>
5efd35b1 23#include <string.h>
e93545dd 24
6a93d84a
TJ
25#include <boost/scoped_array.hpp>
26#include "filefunc.hxx"
27#include "stringfunc.hxx"
28
a287a306
TJ
29
30namespace I2n
6a93d84a 31{
e93545dd
GE
32
33using namespace std;
34
6a93d84a
TJ
35/*
36** implementation of Stat
37*/
38
39Stat::Stat()
a287a306 40: Valid(false)
0107a75b 41{
a287a306 42 clear();
6a93d84a
TJ
43} // eo Stat::Stat()
44
45
46Stat::Stat(const std::string& path, bool follow_links)
47{
48 stat(path,follow_links);
49} // eo Stat::Stat(const std::string&,bool)
50
51
52Stat::~Stat()
53{
54} // eo Stat::~Stat()
55
56
57/**
58 * @brief updates the internal data.
59 *
60 * In other words: stat()'s the file again.
61 */
62void Stat::recheck()
63{
a287a306 64 if (! Path.empty())
6a93d84a 65 {
a287a306 66 stat(Path, FollowLinks);
6a93d84a
TJ
67 }
68} // eo Stat::recheck()
69
70
71/**
72 * @brief calls stat() or lstat() to get the information for the given path
73 * and stores that information.
74 * @param path the path which should be checked
75 * @param follow_links determine if (symbalic) links should be followed.
76 */
77void Stat::stat(const std::string& path, bool follow_links)
78{
79 clear();
a287a306
TJ
80 Path= path;
81 FollowLinks= follow_links;
6a93d84a
TJ
82 struct stat stat_info;
83 int res;
84 res = ( follow_links ? ::stat(path.c_str(), &stat_info) : ::lstat(path.c_str(), &stat_info) );
85 if ( 0 == res )
86 {
a287a306
TJ
87 Device = stat_info.st_dev;
88 Inode = stat_info.st_ino;
89 Mode = (stat_info.st_mode & ~(S_IFMT));
90 NumLinks = stat_info.st_nlink;
91 Uid = stat_info.st_uid;
92 Gid = stat_info.st_gid;
93 DeviceType = stat_info.st_rdev;
94 Size = stat_info.st_size;
95 Atime = stat_info.st_atime;
96 Mtime = stat_info.st_mtime;
97 Ctime = stat_info.st_atime;
98
99 IsLink= S_ISLNK( stat_info.st_mode );
100 IsRegular= S_ISREG( stat_info.st_mode );
101 IsDirectory= S_ISDIR( stat_info.st_mode );
102 IsCharacterDevice= S_ISCHR( stat_info.st_mode );
6ab3bc95 103 IsBlockDevice= S_ISBLK( stat_info.st_mode );
a287a306
TJ
104 IsFifo= S_ISFIFO( stat_info.st_mode );
105 IsSocket= S_ISSOCK( stat_info.st_mode );
6a93d84a 106 }
a287a306 107 Valid = (0 == res);
6a93d84a 108} // eo Stat::stat(const std::string&,bool)
0a654ec0 109
0107a75b 110
6a93d84a
TJ
111/**
112 * @brief clears the internal data.
113 */
114void Stat::clear()
115{
a287a306
TJ
116 Path.clear();
117 Valid= false;
118
119 Device = 0;
120 Inode = 0;
121 Mode = 0;
122 NumLinks = 0;
123 Uid = 0;
124 Gid = 0;
125 DeviceType = 0;
126 Size = 0;
127 Atime = 0;
128 Mtime = 0;
129 Ctime = 0;
130
131 IsLink= false;
132 IsRegular= false;
133 IsDirectory= false;
134 IsCharacterDevice= false;
135 IsBlockDevice= false;
136 IsFifo= false;
137 IsSocket= false;
6a93d84a
TJ
138} // eo Stat::clear()
139
140
141/**
142 * @brief checks if another instance describes the same file.
143 * @param rhs the other instance.
144 * @return @a true iff the other instance describes the same file.
145 * @note
146 * The "same file" means that the files are located on the same device and use the same inode.
147 * They might still have two different directory entries (different paths)!
148 */
a287a306 149bool Stat::is_same_as(const Stat& rhs)
6a93d84a 150{
a287a306
TJ
151 return Valid and rhs.Valid
152 and ( Device == rhs.Device)
153 and ( Inode == rhs.Inode);
154} // eo Stat::is_same_as(const Stat& rhs);
6a93d84a
TJ
155
156
157/**
158 * @brief checks if this and the other instance describe the same device.
159 * @param rhs the other instance.
160 * @return @a true if we and the other instance describe a device and the same device.
161 *
162 * "Same device" means that the devices have the same type and the same major and minor id.
163 */
a287a306 164bool Stat::is_same_device_as(const Stat& rhs)
6a93d84a 165{
a287a306
TJ
166 return is_device() and rhs.is_device()
167 and ( IsBlockDevice == rhs.IsBlockDevice )
168 and ( IsCharacterDevice == rhs.IsCharacterDevice )
169 and ( DeviceType == rhs.DeviceType);
170} // eo Stat::is_same_device_as(const Stat&)
6a93d84a
TJ
171
172/**
173 * @brief check existence of a path.
174 * @param path path which should be tested.
175 * @return @a true iff path exists.
176 */
177bool path_exists(const std::string& path)
178{
179 struct stat stat_info;
180 int res = ::stat(path.c_str(), &stat_info);
181 if (res) return false;
182 return true;
183} // eo path_exists(const std::string&)
184
185/**
186 * @brief check existence of a regular file.
187 * @param path path which should be tested.
188 * @return @a true if path exists and is a regular file (or a link pointing to a regular file).
189 * @note this checks for regular files; not for the pure exitsnace of a path; use pathExists() for that.
190 * @see pathExists
191 */
192bool file_exists(const std::string& path)
193{
194 struct stat stat_info;
195 int res = ::stat(path.c_str(), &stat_info);
196 if (res) return false;
197 return S_ISREG(stat_info.st_mode);
198} // eo file_exists(const std::string&)
199
200// TODO: Use Stat class
201/**
202 * Get size of file
203 * @param name filename to get size for
204 * @return file size or -1 if file does not exist
205 */
206long file_size (const string &name)
e93545dd
GE
207{
208 long iReturn = -1;
0a654ec0 209
e93545dd 210 struct stat statbuff;
0a654ec0 211
e93545dd
GE
212 if (lstat(name.c_str(), &statbuff) < 0)
213 return -1;
214
215 if (!S_ISREG(statbuff.st_mode))
216 return -1;
0a654ec0 217
e93545dd
GE
218 iReturn=statbuff.st_size;
219
220 return iReturn;
221}
222
6a93d84a
TJ
223/**
224 * @brief tests the last modification time stamp of a path.
225 * @param path path which should be tested.
226 * @return the last modification time or 0 if the path doen't exist.
227 */
a287a306 228time_t file_mtime(const std::string& path)
e93545dd 229{
6a93d84a
TJ
230 struct stat stat_info;
231 int res = ::stat(path.c_str(), &stat_info);
232 if (res) return 0;
233 return stat_info.st_mtime;
6ab3bc95 234} // eo file_mtime(const std::string&)
e93545dd 235
e93545dd 236
6a93d84a
TJ
237/**
238 * @brief reads the contents of a directory.
239 * @param path the path to the directory whose contents should be read.
240 * @param[out] result the resulting list of names.
241 * @param include_dot_names determines if dot-files should be included in the list.
242 * @return @a true if reading the directory was succesful, @a false on error.
243 */
a287a306 244bool get_dir(
6a93d84a
TJ
245 const std::string& path,
246 std::vector< std::string >& result,
247 bool include_dot_names )
248{
249 DIR* dir = ::opendir( path.c_str());
250 if (!dir)
251 {
252 return false;
253 }
254 struct dirent *entry;
255 while ( NULL != (entry = ::readdir(dir)) )
e93545dd 256 {
6a93d84a
TJ
257 std::string name( entry->d_name );
258 if (! include_dot_names && (name[0] == '.') )
259 {
260 continue;
261 }
262 result.push_back( name );
e93545dd 263 }
6a93d84a
TJ
264 ::closedir(dir);
265 return true;
6ab3bc95 266} // eo get_dir(const std::string&,std::vector< std::string >&,bool)
6a93d84a
TJ
267
268
269/**
270 * @brief reads the contents of a directory
271 * @param path the path to the directory whose contents should be read.
272 * @param include_dot_names determines if dot-files should be included in the list.
273 * @return the list of names (empty on error).
274 */
a287a306 275std::vector< std::string > get_dir(const std::string& path, bool include_dot_names )
6a93d84a
TJ
276{
277 std::vector< std::string > result;
a287a306 278 get_dir(path,result,include_dot_names);
6a93d84a 279 return result;
6ab3bc95 280} // eo get_dir(const std::string&,bool)
e93545dd 281
e93545dd 282
e93545dd 283
6a93d84a
TJ
284/**
285 * @brief removes a file from a filesystem.
286 * @param path path to the file.
287 * @return @a true iff the unlink was successful.
288 */
289bool unlink( const std::string& path )
e93545dd 290{
6a93d84a
TJ
291 int res = ::unlink( path.c_str() );
292 return (res == 0);
293} // eo unlink(const std::string&)
294
e93545dd 295
e93545dd 296
6a93d84a
TJ
297/**
298 * @brief creates a symbolic link named @a link_name to @a target.
299 * @param target the target the link should point to.
300 * @param link_name the name of the link.
301 * @param force if @a true, the (file or link) @a link_name is removed if it exists.
302 * @return @a true iff the symlink was successfully created.
303 */
304bool symlink(const std::string& target, const std::string& link_name, bool force)
305{
306 int res= -1;
307 if (target.empty() or link_name.empty() or target == link_name)
308 {
309 // no, we don't do this!
e93545dd 310 return false;
6a93d84a
TJ
311 }
312 std::string n_target;
313 if (target[0] == '/') // absolute target?
314 {
315 n_target= target;
316 }
317 else // relative target
318 {
319 // for stat'ing: prepend dir of link_name:
320 n_target= dirname(link_name)+"/"+ target;
321 }
322 Stat target_stat(n_target, false);
323 Stat link_name_stat(link_name, false);
324 if (target_stat.exists() && link_name_stat.exists())
325 {
a287a306
TJ
326 if (link_name_stat.is_same_as(target_stat)
327 or link_name_stat.is_same_device_as(target_stat) )
6a93d84a
TJ
328 {
329 return false;
330 }
331 //TODO: more consistency checks?!
332 }
333 if (link_name_stat.exists())
334 {
335 // the link name already exists.
336 if (force)
337 {
338 // "force" as given, so try to remove the link_name:
339 unlink(link_name);
340 // update the stat:
341 link_name_stat.recheck();
342 }
343 }
344 if (link_name_stat.exists())
345 {
346 // well, if the link_name still exists; we cannot create that link:
347 errno = EEXIST;
348 return false;
349 }
350 res= ::symlink(target.c_str(), link_name.c_str());
351 return (0 == res);
352} // eo symlink(const std::string&,const std::string&,bool)
353
e93545dd 354
6a93d84a
TJ
355
356/**
357 * @brief reads the target of a symbolic link.
358 * @param path path to the symbolic link
359 * @return the target of the link or an empty string on error.
360 */
a287a306 361std::string read_link(const std::string& path)
6a93d84a
TJ
362{
363 errno= 0;
364 Stat stat(path,false);
a287a306 365 if (!stat || !stat.is_link())
6a93d84a
TJ
366 {
367 return std::string();
368 }
369 int buffer_size= PATH_MAX+1 + 128;
370 boost::scoped_array<char> buffer_ptr( new char[buffer_size] );
371 int res= ::readlink( path.c_str(), buffer_ptr.get(), buffer_size-1 );
372 if (res >= 0)
373 {
374 return std::string( buffer_ptr.get(), res );
375 }
376 return std::string();
6ab3bc95 377} // eo read_link(const std::string&)
6a93d84a
TJ
378
379
380
381/**
382 * @brief returns content of a file as string.
383 *
384 * A simple (q'n'd) function for retrieving content of a file as string.<br>
385 * Also able to read special (but regular) files which don't provide a size when stat'ed
386 * (like files in the /proc filesystem).
387 *
388 * @param path path to the file.
389 * @return the content of the file as string (empty if file could be opened).
390 */
a287a306 391std::string read_file(const std::string& path)
6a93d84a
TJ
392{
393 Stat stat(path);
a287a306 394 if (!stat.is_reg())
6a93d84a
TJ
395 {
396 return std::string();
397 }
398 std::ifstream f( path.c_str(), std::ios::in | std::ios::binary );
399 std::string result;
400 if (f.is_open())
401 {
402 // NOTE: there are cases where we don't know the correct size (/proc files...)
403 // therefore we only use the size for reserving space if we know it, but don't
404 // use it when reading the file!
405 if (stat.size() > 0)
406 {
407 // if we know the size, we reserve enough space.
408 result.reserve( stat.size() );
409 }
410 char buffer[2048];
411 while (f.good())
412 {
413 f.read(buffer, sizeof(buffer));
414 result.append(buffer, f.gcount());
415 }
416 }
417 return result;
6ab3bc95 418} // eo read_file(const std::string&)
6a93d84a
TJ
419
420
421/**
422 * @brief writes a string to a file.
423 * @param path path to the file
424 * @param data the data which should be written into the file
425 * @return @a true if the data was written to the file.
426 *
427 * A simple (q'n'd) function for writing a string to a file.
428 */
a287a306 429bool write_file(const std::string& path, const std::string& data)
6a93d84a
TJ
430{
431 std::ofstream f( path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
432 if (f.good())
433 {
434 f.write( data.data(), data.size() );
435 return f.good();
436 }
e93545dd 437 else
6a93d84a 438 {
e93545dd 439 return false;
6a93d84a 440 }
6ab3bc95 441} // eo write_file(const std::string&,const std::string&)
6a93d84a
TJ
442
443
444/**
1a2fc4e8
TJ
445 * Copy file in 4k blocks from source to target.
446 * Overwrites the target if it already exists.
447 *
448 * On error the target file gets removed.
449 *
450 * @param src source file
451 * @param dest target file
452 * @return true if all is ok, false on error
453 */
454bool copy_file(const std::string& src, const std::string& dest)
455{
456 std::ifstream input( src.c_str(), std::ios::in | std::ios::binary );
457 if (!input)
458 return false;
459
460 std::ofstream output( dest.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
461 if (!output)
462 return false;
463
464 char buffer[4096];
465 while (input.good())
466 {
467 input.read(buffer, sizeof(buffer));
468 output.write(buffer, input.gcount());
469
470 // Out of disc space?
471 if (!output.good())
472 {
473 output.close();
474 unlink(dest);
475 return false;
476 }
477 }
478
479 return true;
480}
481
482/**
6a93d84a
TJ
483 * @brief returns the filename part of a path (last component)
484 * @param path the path.
485 * @return the last component of the path.
486 */
487std::string basename(const std::string& path)
488{
489 std::string::size_type pos= path.rfind('/');
490 if (pos != std::string::npos)
491 {
492 return path.substr(pos+1);
493 }
494 return path;
495} // eo basename(const std::string&)
496
497
498/**
499 * @brief returns the directory part of a path.
500 * @param path the path.
501 * @return the directory part of the path.
502 */
503std::string dirname(const std::string& path)
504{
505 std::string::size_type pos= path.rfind('/');
506 if (pos != std::string::npos)
507 {
508 std::string result(path,0,pos);
509 if (result.empty())
510 {
511 return ".";
512 }
513 return result;
514 }
515 return ".";
516} // eo dirname(const std::string&)
517
518
519/**
520 * @brief normalizes a path.
521 *
522 * This method removes empty and "." elements.
523 * It also resolves ".." parts by removing previous path elements if possible.
524 * Leading ".." elements are preserved when a relative path was given; else they are removed.
525 * Trailing slashes are removed.
526 *
527 * @param path the path which should be normalized.
528 * @return the normalized path.
529 */
530
a287a306 531std::string normalize_path(const std::string& path)
6a93d84a
TJ
532{
533 if (path.empty())
534 {
535 return std::string();
536 }
537 // remember if the given path was absolute since this information vanishes when
538 // we split the path (since we split with omitting empty parts...)
539 bool is_absolute= (path[0]=='/');
540 std::list< std::string > parts;
541 std::list< std::string > result_parts;
542
6ab3bc95 543 split_string(path,parts,"/",true);
6a93d84a
TJ
544
545 for(std::list< std::string >::const_iterator it_parts= parts.begin();
546 it_parts != parts.end();
547 ++it_parts)
548 {
549 std::string part(*it_parts); //convenience..
550 if (part == std::string(".") )
551 {
552 // single dot is "current path"; ignore!
553 continue;
554 }
555 if (part == std::string("..") )
556 {
557 // double dot is "one part back"
558 if (result_parts.empty())
559 {
560 if (is_absolute)
561 {
562 // ignore since we cannot move behind / on absolute paths...
563 }
564 else
565 {
566 // on relative path, we need to store the "..":
567 result_parts.push_back(part);
568 }
569 }
570 else if (result_parts.back() == std::string("..") )
571 {
572 // if last element was already "..", we need to store the new one again...
573 // (PS: no need for "absolute" check; this can only be the case on relative path)
574 result_parts.push_back(part);
575 }
576 else
577 {
578 // remove last element.
579 result_parts.pop_back();
580 }
581 continue;
582 }
583 result_parts.push_back(part);
584 }
585 std::string result;
586 if (is_absolute)
587 {
588 result= "/";
589 }
6ab3bc95 590 result+= join_string(result_parts,"/");
6a93d84a 591 return result;
6ab3bc95 592} // eo normalize_path(const std::string&)
6a93d84a
TJ
593
594
595/**
596 * @brief changes the file(/path) mode.
597 * @param path the path to change the mode for.
598 * @param mode the new file mode.
599 * @return @a true iff the file mode was sucessfully changed.
600 */
601bool chmod(const std::string& path, int mode)
602{
603 int res= ::chmod(path.c_str(), mode);
604 return (res==0);
605} // eo chmod(const std::string&,int)
606
607
608/**
609 * @brief changed the owner of a file(/path)
610 * @param path the path to change the owner for.
611 * @param user the new file owner.
612 * @param group the new file group.
613 * @return @a true iff the file owner was succesfully changed.
614 *
615 * @note
616 * the validity of user and group within the system is not checked.
617 * This is intentional since this way we can use id's which are not assigned.
618 */
a287a306 619bool chown(const std::string& path, const I2n::User& user, const I2n::Group& group)
6a93d84a 620{
a287a306 621 uid_t uid= user.Uid;
6a93d84a 622 if (uid<0) return false;
a287a306
TJ
623 gid_t gid= group.Gid;
624 if (gid<0) gid= user.Gid;
6a93d84a
TJ
625 if (gid<0) return false;
626 int res= ::chown( path.c_str(), uid, gid);
627 return (res==0);
628} // eo chown(const std::string&,const User&,const Group&)
e93545dd 629
0a654ec0
TJ
630/**
631 * Recursive delete of files and directories
632 * @param path File or directory to delete
633 * @param error Will contain the error if the return value is false
634 * @return true on success, false otherwise
635 */
636bool recursive_delete(const std::string &path, std::string *error)
637{
638 bool rtn = true;
639
640 try {
641 struct stat my_stat;
642 if (stat(path.c_str(), &my_stat) != 0) {
643 throw runtime_error("can't stat " + path);
644 }
645
646 if (S_ISDIR(my_stat.st_mode)) {
647 DIR *dir = opendir(path.c_str());
648 if (!dir) {
649 throw runtime_error("can't open directory " + path);
650 }
651
652 struct dirent *entry;
653 while ((entry = readdir(dir))) {
654 string filename = entry->d_name;
655 if (filename == "." || filename == "..") {
656 continue;
657 }
658
659 // Delete subdir or file.
660 rtn = recursive_delete(path + "/" + filename, error);
661 if (rtn == false) {
662 break;
663 }
664 }
665
666 closedir(dir);
667 if (rmdir(path.c_str()) != 0) {
668 throw runtime_error("can't remove directory " + path);
669 }
670 } else {
671 if (unlink(path.c_str()) != 0) {
672 throw runtime_error("can't unlink " + path);
673 }
674 }
675 } catch (exception &e) {
676 if (error) {
677 ostringstream out;
678 out << e.what() << " (" << strerror(errno) << ")";
679 *error = out.str();
680 }
681 rtn = false;
682 } catch (...) {
683 if (error) {
684 ostringstream out;
685 out << "unknown error (" << strerror(errno) << ")";
686 *error = out.str();
687 }
688 rtn = false;
689 }
e93545dd 690
0a654ec0 691 return rtn;
6ab3bc95 692} // eo recursive_delete(const std::string&,std::string*)
6a93d84a 693
6ab3bc95 694} // eo namespace I2n