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