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