New function to check if two files differ: file_content_differs()
[libi2ncommon] / src / filefunc.cpp
CommitLineData
0e23f538
TJ
1/*
2The software in this package is distributed under the GNU General
3Public License version 2 (with a special exception described below).
4
5A copy of GNU General Public License (GPL) is included in this distribution,
6in the file COPYING.GPL.
7
8As a special exception, if other files instantiate templates or use macros
9or inline functions from this file, or you compile this file and link it
10with other works to produce a work based on this file, this file
11does not by itself cause the resulting work to be covered
12by the GNU General Public License.
13
14However the source code for this file must still be made available
15in accordance with section (3) of the GNU General Public License.
16
17This exception does not invalidate any other reasons why a work based
18on this file might be covered by the GNU General Public License.
19*/
e93545dd
GE
20/***************************************************************************
21 escape.cpp - escaping of strings
22 -------------------
23 begin : Sun Nov 14 1999
24 copyright : (C) 1999 by Intra2net AG
e93545dd
GE
25 ***************************************************************************/
26
6a93d84a 27#include <list>
e93545dd 28#include <string>
6a93d84a 29#include <fstream>
0a654ec0 30#include <sstream>
e93545dd 31#include <iostream>
0a654ec0 32#include <stdexcept>
e93545dd
GE
33
34#include <sys/types.h>
35#include <sys/stat.h>
0a654ec0 36#include <dirent.h>
e93545dd
GE
37#include <pwd.h>
38#include <grp.h>
39#include <unistd.h>
0a654ec0 40#include <errno.h>
5efd35b1 41#include <string.h>
e93545dd 42
6a93d84a 43#include <boost/scoped_array.hpp>
deab8f59 44#include <boost/foreach.hpp>
6a93d84a
TJ
45#include "filefunc.hxx"
46#include "stringfunc.hxx"
47
a287a306 48namespace I2n
6a93d84a 49{
e93545dd
GE
50
51using namespace std;
52
6a93d84a
TJ
53/*
54** implementation of Stat
55*/
56
57Stat::Stat()
027e0a1c
TJ
58 : FollowLinks(true)
59 , Valid(false)
0107a75b 60{
a287a306 61 clear();
6a93d84a
TJ
62} // eo Stat::Stat()
63
64
65Stat::Stat(const std::string& path, bool follow_links)
66{
67 stat(path,follow_links);
68} // eo Stat::Stat(const std::string&,bool)
69
70
71Stat::~Stat()
72{
73} // eo Stat::~Stat()
74
75
76/**
77 * @brief updates the internal data.
78 *
79 * In other words: stat()'s the file again.
80 */
81void Stat::recheck()
82{
a287a306 83 if (! Path.empty())
6a93d84a 84 {
96fe2dc5
GE
85 // pass a copy of Path: otherwise clear() would leave an empty reference
86 stat(string(Path), FollowLinks);
6a93d84a
TJ
87 }
88} // eo Stat::recheck()
89
90
91/**
92 * @brief calls stat() or lstat() to get the information for the given path
93 * and stores that information.
94 * @param path the path which should be checked
95 * @param follow_links determine if (symbalic) links should be followed.
96 */
97void Stat::stat(const std::string& path, bool follow_links)
98{
99 clear();
a287a306
TJ
100 Path= path;
101 FollowLinks= follow_links;
6a93d84a
TJ
102 struct stat stat_info;
103 int res;
104 res = ( follow_links ? ::stat(path.c_str(), &stat_info) : ::lstat(path.c_str(), &stat_info) );
105 if ( 0 == res )
106 {
a287a306
TJ
107 Device = stat_info.st_dev;
108 Inode = stat_info.st_ino;
109 Mode = (stat_info.st_mode & ~(S_IFMT));
110 NumLinks = stat_info.st_nlink;
111 Uid = stat_info.st_uid;
112 Gid = stat_info.st_gid;
113 DeviceType = stat_info.st_rdev;
114 Size = stat_info.st_size;
115 Atime = stat_info.st_atime;
116 Mtime = stat_info.st_mtime;
117 Ctime = stat_info.st_atime;
118
119 IsLink= S_ISLNK( stat_info.st_mode );
120 IsRegular= S_ISREG( stat_info.st_mode );
121 IsDirectory= S_ISDIR( stat_info.st_mode );
122 IsCharacterDevice= S_ISCHR( stat_info.st_mode );
6ab3bc95 123 IsBlockDevice= S_ISBLK( stat_info.st_mode );
a287a306
TJ
124 IsFifo= S_ISFIFO( stat_info.st_mode );
125 IsSocket= S_ISSOCK( stat_info.st_mode );
6a93d84a 126 }
a287a306 127 Valid = (0 == res);
6a93d84a 128} // eo Stat::stat(const std::string&,bool)
0a654ec0 129
0107a75b 130
6a93d84a
TJ
131/**
132 * @brief clears the internal data.
133 */
134void Stat::clear()
135{
a287a306
TJ
136 Path.clear();
137 Valid= false;
138
139 Device = 0;
140 Inode = 0;
141 Mode = 0;
142 NumLinks = 0;
143 Uid = 0;
144 Gid = 0;
145 DeviceType = 0;
146 Size = 0;
147 Atime = 0;
148 Mtime = 0;
149 Ctime = 0;
150
151 IsLink= false;
152 IsRegular= false;
153 IsDirectory= false;
154 IsCharacterDevice= false;
155 IsBlockDevice= false;
156 IsFifo= false;
157 IsSocket= false;
6a93d84a
TJ
158} // eo Stat::clear()
159
160
161/**
162 * @brief checks if another instance describes the same file.
163 * @param rhs the other instance.
164 * @return @a true iff the other instance describes the same file.
165 * @note
166 * The "same file" means that the files are located on the same device and use the same inode.
167 * They might still have two different directory entries (different paths)!
168 */
a287a306 169bool Stat::is_same_as(const Stat& rhs)
6a93d84a 170{
a287a306
TJ
171 return Valid and rhs.Valid
172 and ( Device == rhs.Device)
173 and ( Inode == rhs.Inode);
174} // eo Stat::is_same_as(const Stat& rhs);
6a93d84a
TJ
175
176
177/**
178 * @brief checks if this and the other instance describe the same device.
179 * @param rhs the other instance.
180 * @return @a true if we and the other instance describe a device and the same device.
181 *
182 * "Same device" means that the devices have the same type and the same major and minor id.
183 */
a287a306 184bool Stat::is_same_device_as(const Stat& rhs)
6a93d84a 185{
a287a306
TJ
186 return is_device() and rhs.is_device()
187 and ( IsBlockDevice == rhs.IsBlockDevice )
188 and ( IsCharacterDevice == rhs.IsCharacterDevice )
189 and ( DeviceType == rhs.DeviceType);
190} // eo Stat::is_same_device_as(const Stat&)
6a93d84a
TJ
191
192/**
193 * @brief check existence of a path.
194 * @param path path which should be tested.
195 * @return @a true iff path exists.
196 */
197bool path_exists(const std::string& path)
198{
199 struct stat stat_info;
200 int res = ::stat(path.c_str(), &stat_info);
201 if (res) return false;
202 return true;
203} // eo path_exists(const std::string&)
204
205/**
206 * @brief check existence of a regular file.
207 * @param path path which should be tested.
208 * @return @a true if path exists and is a regular file (or a link pointing to a regular file).
209 * @note this checks for regular files; not for the pure exitsnace of a path; use pathExists() for that.
210 * @see pathExists
211 */
212bool file_exists(const std::string& path)
213{
214 struct stat stat_info;
215 int res = ::stat(path.c_str(), &stat_info);
216 if (res) return false;
217 return S_ISREG(stat_info.st_mode);
218} // eo file_exists(const std::string&)
219
220// TODO: Use Stat class
221/**
222 * Get size of file
223 * @param name filename to get size for
224 * @return file size or -1 if file does not exist
225 */
226long file_size (const string &name)
e93545dd
GE
227{
228 long iReturn = -1;
0a654ec0 229
e93545dd 230 struct stat statbuff;
0a654ec0 231
e93545dd
GE
232 if (lstat(name.c_str(), &statbuff) < 0)
233 return -1;
234
235 if (!S_ISREG(statbuff.st_mode))
236 return -1;
0a654ec0 237
e93545dd
GE
238 iReturn=statbuff.st_size;
239
240 return iReturn;
241}
242
6a93d84a
TJ
243/**
244 * @brief tests the last modification time stamp of a path.
245 * @param path path which should be tested.
246 * @return the last modification time or 0 if the path doen't exist.
247 */
a287a306 248time_t file_mtime(const std::string& path)
e93545dd 249{
6a93d84a
TJ
250 struct stat stat_info;
251 int res = ::stat(path.c_str(), &stat_info);
252 if (res) return 0;
253 return stat_info.st_mtime;
6ab3bc95 254} // eo file_mtime(const std::string&)
e93545dd 255
e93545dd 256
6a93d84a 257/**
9d1eb8c8
TJ
258 * @brief Check if two files differ
259 *
260 * Note: Reads the whole file into memory
261 * if the file size is identical.
262 *
263 * @param old_filename Filename of old file
264 * @param new_filename Filename of new file
265 *
266 * @return bool True if files differ, false otherwise.
267 * If one file does not exist, also returns true
268 */
269bool file_content_differs(const std::string &old_filename, const std::string &new_filename)
270{
271 if (I2n::file_exists(old_filename) == false ||
272 I2n::file_exists(new_filename) == false)
273 return true;
274
275 // check if size differs
276 if (I2n::file_size(old_filename) != I2n::file_size(new_filename))
277 return true;
278
279 const std::string old_content = I2n::read_file(old_filename);
280 const std::string new_content = I2n::read_file(new_filename);
281
282 // check if content differs
283 if (old_content == new_content)
284 return false;
285
286 // Differ by default (fallback)
287 return true;
288}
289
290
291/**
6a93d84a
TJ
292 * @brief reads the contents of a directory.
293 * @param path the path to the directory whose contents should be read.
294 * @param[out] result the resulting list of names.
295 * @param include_dot_names determines if dot-files should be included in the list.
296 * @return @a true if reading the directory was succesful, @a false on error.
297 */
a287a306 298bool get_dir(
6a93d84a
TJ
299 const std::string& path,
300 std::vector< std::string >& result,
301 bool include_dot_names )
302{
303 DIR* dir = ::opendir( path.c_str());
304 if (!dir)
305 {
306 return false;
307 }
10c3af2d
TJ
308 struct dirent store, *entry = NULL;
309 while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
e93545dd 310 {
6a93d84a
TJ
311 std::string name( entry->d_name );
312 if (! include_dot_names && (name[0] == '.') )
313 {
314 continue;
315 }
316 result.push_back( name );
e93545dd 317 }
6a93d84a
TJ
318 ::closedir(dir);
319 return true;
6ab3bc95 320} // eo get_dir(const std::string&,std::vector< std::string >&,bool)
6a93d84a
TJ
321
322
323/**
324 * @brief reads the contents of a directory
325 * @param path the path to the directory whose contents should be read.
326 * @param include_dot_names determines if dot-files should be included in the list.
327 * @return the list of names (empty on error).
328 */
a287a306 329std::vector< std::string > get_dir(const std::string& path, bool include_dot_names )
6a93d84a
TJ
330{
331 std::vector< std::string > result;
a287a306 332 get_dir(path,result,include_dot_names);
6a93d84a 333 return result;
6ab3bc95 334} // eo get_dir(const std::string&,bool)
e93545dd 335
e93545dd 336
e93545dd 337
6a93d84a
TJ
338/**
339 * @brief removes a file from a filesystem.
340 * @param path path to the file.
44725532 341 * @return @a true if the unlink was successful.
6a93d84a
TJ
342 */
343bool unlink( const std::string& path )
e93545dd 344{
6a93d84a
TJ
345 int res = ::unlink( path.c_str() );
346 return (res == 0);
347} // eo unlink(const std::string&)
348
e93545dd 349
e93545dd 350
6a93d84a
TJ
351/**
352 * @brief creates a symbolic link named @a link_name to @a target.
353 * @param target the target the link should point to.
354 * @param link_name the name of the link.
355 * @param force if @a true, the (file or link) @a link_name is removed if it exists.
356 * @return @a true iff the symlink was successfully created.
357 */
358bool symlink(const std::string& target, const std::string& link_name, bool force)
359{
360 int res= -1;
361 if (target.empty() or link_name.empty() or target == link_name)
362 {
363 // no, we don't do this!
e93545dd 364 return false;
6a93d84a
TJ
365 }
366 std::string n_target;
367 if (target[0] == '/') // absolute target?
368 {
369 n_target= target;
370 }
371 else // relative target
372 {
373 // for stat'ing: prepend dir of link_name:
374 n_target= dirname(link_name)+"/"+ target;
375 }
376 Stat target_stat(n_target, false);
377 Stat link_name_stat(link_name, false);
378 if (target_stat.exists() && link_name_stat.exists())
379 {
a287a306
TJ
380 if (link_name_stat.is_same_as(target_stat)
381 or link_name_stat.is_same_device_as(target_stat) )
6a93d84a
TJ
382 {
383 return false;
384 }
385 //TODO: more consistency checks?!
386 }
387 if (link_name_stat.exists())
388 {
389 // the link name already exists.
390 if (force)
391 {
392 // "force" as given, so try to remove the link_name:
393 unlink(link_name);
394 // update the stat:
395 link_name_stat.recheck();
396 }
397 }
398 if (link_name_stat.exists())
399 {
400 // well, if the link_name still exists; we cannot create that link:
401 errno = EEXIST;
402 return false;
403 }
404 res= ::symlink(target.c_str(), link_name.c_str());
405 return (0 == res);
406} // eo symlink(const std::string&,const std::string&,bool)
407
e93545dd 408
6a93d84a
TJ
409
410/**
411 * @brief reads the target of a symbolic link.
412 * @param path path to the symbolic link
413 * @return the target of the link or an empty string on error.
414 */
a287a306 415std::string read_link(const std::string& path)
6a93d84a
TJ
416{
417 errno= 0;
418 Stat stat(path,false);
a287a306 419 if (!stat || !stat.is_link())
6a93d84a
TJ
420 {
421 return std::string();
422 }
423 int buffer_size= PATH_MAX+1 + 128;
424 boost::scoped_array<char> buffer_ptr( new char[buffer_size] );
425 int res= ::readlink( path.c_str(), buffer_ptr.get(), buffer_size-1 );
426 if (res >= 0)
427 {
428 return std::string( buffer_ptr.get(), res );
429 }
430 return std::string();
6ab3bc95 431} // eo read_link(const std::string&)
6a93d84a
TJ
432
433
434
435/**
436 * @brief returns content of a file as string.
437 *
438 * A simple (q'n'd) function for retrieving content of a file as string.<br>
439 * Also able to read special (but regular) files which don't provide a size when stat'ed
440 * (like files in the /proc filesystem).
441 *
442 * @param path path to the file.
443 * @return the content of the file as string (empty if file could be opened).
444 */
a287a306 445std::string read_file(const std::string& path)
6a93d84a
TJ
446{
447 Stat stat(path);
a287a306 448 if (!stat.is_reg())
6a93d84a
TJ
449 {
450 return std::string();
451 }
452 std::ifstream f( path.c_str(), std::ios::in | std::ios::binary );
453 std::string result;
454 if (f.is_open())
455 {
456 // NOTE: there are cases where we don't know the correct size (/proc files...)
457 // therefore we only use the size for reserving space if we know it, but don't
458 // use it when reading the file!
459 if (stat.size() > 0)
460 {
461 // if we know the size, we reserve enough space.
462 result.reserve( stat.size() );
463 }
464 char buffer[2048];
465 while (f.good())
466 {
467 f.read(buffer, sizeof(buffer));
468 result.append(buffer, f.gcount());
469 }
470 }
471 return result;
6ab3bc95 472} // eo read_file(const std::string&)
6a93d84a
TJ
473
474
475/**
476 * @brief writes a string to a file.
477 * @param path path to the file
478 * @param data the data which should be written into the file
71f7bfb8 479 * @param trunc set the trunc flag when opening the file. Do not use for files in /proc and /sys
6a93d84a
TJ
480 * @return @a true if the data was written to the file.
481 *
482 * A simple (q'n'd) function for writing a string to a file.
483 */
71f7bfb8 484bool write_file(const std::string& path, const std::string& data, bool trunc)
6a93d84a 485{
71f7bfb8
GE
486 // set the correct openmode flags
487 std::ios_base::openmode flags = std::ios::out | std::ios::binary;
488 if (trunc)
489 flags |= std::ios::trunc;
490
491 std::ofstream f( path.c_str(), flags);
6a93d84a
TJ
492 if (f.good())
493 {
494 f.write( data.data(), data.size() );
495 return f.good();
496 }
e93545dd 497 else
6a93d84a 498 {
e93545dd 499 return false;
6a93d84a 500 }
6ab3bc95 501} // eo write_file(const std::string&,const std::string&)
6a93d84a
TJ
502
503
504/**
1a2fc4e8
TJ
505 * Copy file in 4k blocks from source to target.
506 * Overwrites the target if it already exists.
507 *
508 * On error the target file gets removed.
509 *
510 * @param src source file
511 * @param dest target file
512 * @return true if all is ok, false on error
513 */
514bool copy_file(const std::string& src, const std::string& dest)
515{
a1e7d2a7
GE
516 std::ifstream input( src.c_str(), std::ios::in | std::ios::binary );
517 if (!input)
518 return false;
519
520 std::ofstream output( dest.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
521 if (!output)
522 return false;
523
524 // Out of disc space?
525 if (!copy_stream(input,output))
526 {
527 output.close();
528 unlink(dest);
529 return false;
530 }
531
532 return true;
533}
534
535/**
536 * Copy streams in 4k blocks.
537 *
538 * @param is source stream
539 * @param os target stream
540 * @return true if all is ok, false on error
541 */
542bool copy_stream(std::istream& is, std::ostream& os)
543{
544 if (!is)
1a2fc4e8
TJ
545 return false;
546
a1e7d2a7 547 if (!os)
1a2fc4e8
TJ
548 return false;
549
550 char buffer[4096];
a1e7d2a7 551 while (is.good())
1a2fc4e8 552 {
a1e7d2a7
GE
553 is.read(buffer, sizeof(buffer));
554 os.write(buffer, is.gcount());
555
556 // Can't write?
557 if (!os.good())
1a2fc4e8 558 return false;
1a2fc4e8
TJ
559 }
560
561 return true;
562}
563
564/**
6a93d84a
TJ
565 * @brief returns the filename part of a path (last component)
566 * @param path the path.
567 * @return the last component of the path.
568 */
569std::string basename(const std::string& path)
570{
571 std::string::size_type pos= path.rfind('/');
572 if (pos != std::string::npos)
573 {
574 return path.substr(pos+1);
575 }
576 return path;
577} // eo basename(const std::string&)
578
579
580/**
581 * @brief returns the directory part of a path.
582 * @param path the path.
583 * @return the directory part of the path.
584 */
585std::string dirname(const std::string& path)
586{
587 std::string::size_type pos= path.rfind('/');
588 if (pos != std::string::npos)
589 {
590 std::string result(path,0,pos);
591 if (result.empty())
592 {
593 return ".";
594 }
595 return result;
596 }
597 return ".";
598} // eo dirname(const std::string&)
599
600
601/**
602 * @brief normalizes a path.
603 *
604 * This method removes empty and "." elements.
605 * It also resolves ".." parts by removing previous path elements if possible.
606 * Leading ".." elements are preserved when a relative path was given; else they are removed.
607 * Trailing slashes are removed.
608 *
609 * @param path the path which should be normalized.
610 * @return the normalized path.
611 */
612
a287a306 613std::string normalize_path(const std::string& path)
6a93d84a
TJ
614{
615 if (path.empty())
616 {
617 return std::string();
618 }
619 // remember if the given path was absolute since this information vanishes when
620 // we split the path (since we split with omitting empty parts...)
621 bool is_absolute= (path[0]=='/');
622 std::list< std::string > parts;
623 std::list< std::string > result_parts;
624
6ab3bc95 625 split_string(path,parts,"/",true);
6a93d84a
TJ
626
627 for(std::list< std::string >::const_iterator it_parts= parts.begin();
628 it_parts != parts.end();
629 ++it_parts)
630 {
631 std::string part(*it_parts); //convenience..
632 if (part == std::string(".") )
633 {
634 // single dot is "current path"; ignore!
635 continue;
636 }
637 if (part == std::string("..") )
638 {
639 // double dot is "one part back"
640 if (result_parts.empty())
641 {
642 if (is_absolute)
643 {
644 // ignore since we cannot move behind / on absolute paths...
645 }
646 else
647 {
648 // on relative path, we need to store the "..":
649 result_parts.push_back(part);
650 }
651 }
652 else if (result_parts.back() == std::string("..") )
653 {
654 // if last element was already "..", we need to store the new one again...
655 // (PS: no need for "absolute" check; this can only be the case on relative path)
656 result_parts.push_back(part);
657 }
658 else
659 {
660 // remove last element.
661 result_parts.pop_back();
662 }
663 continue;
664 }
665 result_parts.push_back(part);
666 }
667 std::string result;
668 if (is_absolute)
669 {
670 result= "/";
671 }
6ab3bc95 672 result+= join_string(result_parts,"/");
6a93d84a 673 return result;
6ab3bc95 674} // eo normalize_path(const std::string&)
6a93d84a
TJ
675
676
677/**
f002679a
GE
678 * @brief calls fsync on a given directory to sync all it's metadata
679 * @param path the path of the directory.
680 * @return true if successful
681 */
682bool dirsync(const std::string& path)
683{
684 // sync the directory the file is in
685 DIR* dir=opendir(path.c_str());
686 if (dir == NULL)
687 return false;
688
689 int ret=fsync(dirfd(dir));
690
691 closedir(dir);
692
693 return (ret==0);
694}
695
696/**
6a93d84a
TJ
697 * @brief changes the file(/path) mode.
698 * @param path the path to change the mode for.
699 * @param mode the new file mode.
700 * @return @a true iff the file mode was sucessfully changed.
701 */
702bool chmod(const std::string& path, int mode)
703{
704 int res= ::chmod(path.c_str(), mode);
705 return (res==0);
706} // eo chmod(const std::string&,int)
707
708
709/**
710 * @brief changed the owner of a file(/path)
711 * @param path the path to change the owner for.
712 * @param user the new file owner.
713 * @param group the new file group.
714 * @return @a true iff the file owner was succesfully changed.
715 *
716 * @note
717 * the validity of user and group within the system is not checked.
718 * This is intentional since this way we can use id's which are not assigned.
719 */
a287a306 720bool chown(const std::string& path, const I2n::User& user, const I2n::Group& group)
6a93d84a 721{
a287a306 722 uid_t uid= user.Uid;
6a93d84a 723 if (uid<0) return false;
a287a306
TJ
724 gid_t gid= group.Gid;
725 if (gid<0) gid= user.Gid;
6a93d84a
TJ
726 if (gid<0) return false;
727 int res= ::chown( path.c_str(), uid, gid);
728 return (res==0);
729} // eo chown(const std::string&,const User&,const Group&)
e93545dd 730
0a654ec0
TJ
731/**
732 * Recursive delete of files and directories
733 * @param path File or directory to delete
44725532 734 * @param error Will contain the error if the return value is false [optional]
0a654ec0
TJ
735 * @return true on success, false otherwise
736 */
737bool recursive_delete(const std::string &path, std::string *error)
738{
739 bool rtn = true;
740
741 try {
742 struct stat my_stat;
743 if (stat(path.c_str(), &my_stat) != 0) {
744 throw runtime_error("can't stat " + path);
745 }
746
747 if (S_ISDIR(my_stat.st_mode)) {
748 DIR *dir = opendir(path.c_str());
749 if (!dir) {
750 throw runtime_error("can't open directory " + path);
751 }
752
10c3af2d
TJ
753 struct dirent store, *entry = NULL;
754 while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
755 {
0a654ec0
TJ
756 string filename = entry->d_name;
757 if (filename == "." || filename == "..") {
758 continue;
759 }
760
761 // Delete subdir or file.
762 rtn = recursive_delete(path + "/" + filename, error);
763 if (rtn == false) {
764 break;
765 }
766 }
767
768 closedir(dir);
44725532 769 if (!rmdir(path)) {
0a654ec0
TJ
770 throw runtime_error("can't remove directory " + path);
771 }
772 } else {
44725532 773 if (!unlink(path)) {
0a654ec0
TJ
774 throw runtime_error("can't unlink " + path);
775 }
776 }
777 } catch (exception &e) {
778 if (error) {
779 ostringstream out;
780 out << e.what() << " (" << strerror(errno) << ")";
781 *error = out.str();
782 }
783 rtn = false;
784 } catch (...) {
785 if (error) {
786 ostringstream out;
787 out << "unknown error (" << strerror(errno) << ")";
788 *error = out.str();
789 }
790 rtn = false;
791 }
e93545dd 792
0a654ec0 793 return rtn;
6ab3bc95 794} // eo recursive_delete(const std::string&,std::string*)
6a93d84a 795
faf8475b
TJ
796/**
797 Create a unique temporary directory from path_template.
798 @param Path template. The last six characters must be XXXXXX.
44725532 799 @param error Will contain the error if the return value is false [optional]
faf8475b
TJ
800 @return Name of new directory or empty string on error.
801*/
44725532 802std::string mkdtemp(const std::string &path_template, std::string *error)
faf8475b
TJ
803{
804 boost::scoped_array<char> buf( new char[path_template.size()+1] );
805 path_template.copy(buf.get(), path_template.size());
806 buf[path_template.size()]=0;
807
808 char *unique_dir = ::mkdtemp(buf.get());
809 if (!unique_dir)
44725532
TJ
810 {
811 if (error)
812 *error = strerror(errno);
faf8475b 813 return "";
44725532 814 }
faf8475b
TJ
815
816 // Scoped pointer is still valid
817 return std::string(unique_dir);
818}
819
44725532
TJ
820/**
821 Create directory
822 @param path Path to create
823 @param error Will contain the error if the return value is false [optional]
824 @return True on success, false on error
825*/
826bool mkdir(const std::string &path, const mode_t &mode, std::string *error)
827{
828 if ( ::mkdir(path.c_str(), mode) == 0)
829 return true;
830
831 if (error)
832 *error = strerror(errno);
833 return false;
834}
835
836/**
837 Remove directory
838 @param path Path to removed
839 @param error Will contain the error if the return value is false [optional]
840 @return True on successs, false otherwise
841*/
842bool rmdir(const std::string &path, std::string *error)
843{
844 if ( ::rmdir(path.c_str() ) == 0)
845 return true;
846
847 if (error)
848 *error = strerror(errno);
849 return false;
850}
851
852/// Small helper class for scoped free
853class scoped_C_free
854{
855public:
856 scoped_C_free(void *ptr)
857 : pointer_to_free(ptr)
858 {
859 }
860
861 ~scoped_C_free()
862 {
863 free (pointer_to_free);
864 pointer_to_free = NULL;
865 }
866
867private:
868 void *pointer_to_free;
869};
870
871/**
872 Get current working directory
873 @return Current working directory. Empty string on error.
874*/
875std::string getcwd()
876{
877 char *cwd = ::getcwd(NULL, 0);
878 if (!cwd)
879 return "";
880
881 // Make deallocation of cwd exception safe
882 scoped_C_free holder(cwd);
883
884 string current_dir(cwd);
885 return current_dir;
886}
887
888/**
889 Change current working directory
890 @param path Path to change to
891 @param error Will contain the error if the return value is false [optional]
892 @return True on successs, false otherwise
893*/
894bool chdir(const std::string &path, std::string *error)
895{
896 if ( ::chdir(path.c_str() ) == 0)
897 return true;
898
899 if (error)
900 *error = strerror(errno);
901 return false;
902}
faf8475b 903
901e2943
TJ
904/**
905 Set file mode creation mask
906 @param mask Creation mask
907 @return Previous creation mask (function call always succeeds)
908*/
909mode_t umask(mode_t mask)
910{
911 return ::umask(mask);
912}
913
deab8f59
CH
914
915/**
916 * @brief Remove unlisted files
917 *
918 * @param directory Direcotry to look for files
919 * @param keep_files List of files or directories to keep
920 * @param prefix Filename prefix to match. Empty prefix matches all.
921 *
922 * @return bool True if the directory was scanned, false on error (directory not found, permission denied)
923 **/
924bool remove_unlisted_files(const std::string &directory,
925 const std::set<std::string> &keep_files,
926 const std::string &prefix)
927{
928 std::vector<std::string> content;
929 if (!get_dir(directory, content, false))
930 return false;
931
932 bool all_fine = true;
933 BOOST_FOREACH(const std::string &file, content)
934 {
935 // Check for filename prefix (if any)
936 if (!prefix.empty() && file.find(prefix) != 0)
937 continue;
938
939 // Check if file is whitelisted
940 if (keep_files.find(file) != keep_files.end())
941 continue;
942
943 // Try to unlink file. (Continue on error)
944 if (!unlink(directory + "/" + file))
945 all_fine = false;
946 }
947
948 return all_fine;
949}
950
951
6ab3bc95 952} // eo namespace I2n