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