Rename get_dir_size() to get_dir_count()
[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{
3f8a0632 311 // code copied to get_dir_count; keep in sync
6a93d84a
TJ
312 DIR* dir = ::opendir( path.c_str());
313 if (!dir)
314 {
315 return false;
316 }
10c3af2d
TJ
317 struct dirent store, *entry = NULL;
318 while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
e93545dd 319 {
6a93d84a
TJ
320 std::string name( entry->d_name );
321 if (! include_dot_names && (name[0] == '.') )
322 {
323 continue;
324 }
325 result.push_back( name );
e93545dd 326 }
6a93d84a
TJ
327 ::closedir(dir);
328 return true;
6ab3bc95 329} // eo get_dir(const std::string&,std::vector< std::string >&,bool)
6a93d84a
TJ
330
331
332/**
333 * @brief reads the contents of a directory
334 * @param path the path to the directory whose contents should be read.
335 * @param include_dot_names determines if dot-files should be included in the list.
336 * @return the list of names (empty on error).
337 */
a287a306 338std::vector< std::string > get_dir(const std::string& path, bool include_dot_names )
6a93d84a
TJ
339{
340 std::vector< std::string > result;
a287a306 341 get_dir(path,result,include_dot_names);
6a93d84a 342 return result;
6ab3bc95 343} // eo get_dir(const std::string&,bool)
e93545dd 344
e93545dd 345
6569b97d
CH
346/**
347 * @brief count entries in directory, like get_dir(path, include_dot_names).size()
348 * @param path the path to the directory whose contents should be read.
349 * @param include_dot_names determines if dot-files should be included in the count.
350 * @return the number of entries in the directory; return -1 in case of error
351 */
3f8a0632 352int get_dir_count(const std::string& path, bool include_dot_names)
6569b97d
CH
353{
354 // code is a simplified copy of get_dir, above. Keep in sync
355 int result = 0;
356 DIR* dir = ::opendir( path.c_str());
357 if (!dir)
358 return -1;
359 struct dirent store, *entry = NULL;
360 while (readdir_r(dir, &store, &entry) == 0 && entry != NULL)
361 {
362 if (entry->d_name == NULL)
363 continue; // should not happen
364 else if (! include_dot_names && (entry->d_name)[0] == '.')
365 continue;
366 ++result;
367 }
368 ::closedir(dir);
369 return result;
370}
371
e93545dd 372
6a93d84a
TJ
373/**
374 * @brief removes a file from a filesystem.
375 * @param path path to the file.
44725532 376 * @return @a true if the unlink was successful.
6a93d84a
TJ
377 */
378bool unlink( const std::string& path )
e93545dd 379{
6a93d84a
TJ
380 int res = ::unlink( path.c_str() );
381 return (res == 0);
382} // eo unlink(const std::string&)
383
e93545dd 384
e93545dd 385
6a93d84a
TJ
386/**
387 * @brief creates a symbolic link named @a link_name to @a target.
388 * @param target the target the link should point to.
389 * @param link_name the name of the link.
390 * @param force if @a true, the (file or link) @a link_name is removed if it exists.
391 * @return @a true iff the symlink was successfully created.
392 */
393bool symlink(const std::string& target, const std::string& link_name, bool force)
394{
395 int res= -1;
396 if (target.empty() or link_name.empty() or target == link_name)
397 {
398 // no, we don't do this!
e93545dd 399 return false;
6a93d84a
TJ
400 }
401 std::string n_target;
402 if (target[0] == '/') // absolute target?
403 {
404 n_target= target;
405 }
406 else // relative target
407 {
408 // for stat'ing: prepend dir of link_name:
409 n_target= dirname(link_name)+"/"+ target;
410 }
411 Stat target_stat(n_target, false);
412 Stat link_name_stat(link_name, false);
413 if (target_stat.exists() && link_name_stat.exists())
414 {
a287a306
TJ
415 if (link_name_stat.is_same_as(target_stat)
416 or link_name_stat.is_same_device_as(target_stat) )
6a93d84a
TJ
417 {
418 return false;
419 }
420 //TODO: more consistency checks?!
421 }
422 if (link_name_stat.exists())
423 {
424 // the link name already exists.
425 if (force)
426 {
427 // "force" as given, so try to remove the link_name:
428 unlink(link_name);
429 // update the stat:
430 link_name_stat.recheck();
431 }
432 }
433 if (link_name_stat.exists())
434 {
435 // well, if the link_name still exists; we cannot create that link:
436 errno = EEXIST;
437 return false;
438 }
439 res= ::symlink(target.c_str(), link_name.c_str());
440 return (0 == res);
441} // eo symlink(const std::string&,const std::string&,bool)
442
e93545dd 443
6a93d84a
TJ
444
445/**
446 * @brief reads the target of a symbolic link.
447 * @param path path to the symbolic link
448 * @return the target of the link or an empty string on error.
449 */
a287a306 450std::string read_link(const std::string& path)
6a93d84a
TJ
451{
452 errno= 0;
453 Stat stat(path,false);
a287a306 454 if (!stat || !stat.is_link())
6a93d84a
TJ
455 {
456 return std::string();
457 }
458 int buffer_size= PATH_MAX+1 + 128;
459 boost::scoped_array<char> buffer_ptr( new char[buffer_size] );
460 int res= ::readlink( path.c_str(), buffer_ptr.get(), buffer_size-1 );
461 if (res >= 0)
462 {
463 return std::string( buffer_ptr.get(), res );
464 }
465 return std::string();
6ab3bc95 466} // eo read_link(const std::string&)
6a93d84a
TJ
467
468
469
470/**
471 * @brief returns content of a file as string.
472 *
473 * A simple (q'n'd) function for retrieving content of a file as string.<br>
474 * Also able to read special (but regular) files which don't provide a size when stat'ed
475 * (like files in the /proc filesystem).
476 *
477 * @param path path to the file.
478 * @return the content of the file as string (empty if file could be opened).
479 */
a287a306 480std::string read_file(const std::string& path)
6a93d84a
TJ
481{
482 Stat stat(path);
a287a306 483 if (!stat.is_reg())
6a93d84a
TJ
484 {
485 return std::string();
486 }
487 std::ifstream f( path.c_str(), std::ios::in | std::ios::binary );
488 std::string result;
489 if (f.is_open())
490 {
491 // NOTE: there are cases where we don't know the correct size (/proc files...)
492 // therefore we only use the size for reserving space if we know it, but don't
493 // use it when reading the file!
494 if (stat.size() > 0)
495 {
496 // if we know the size, we reserve enough space.
497 result.reserve( stat.size() );
498 }
499 char buffer[2048];
500 while (f.good())
501 {
502 f.read(buffer, sizeof(buffer));
503 result.append(buffer, f.gcount());
504 }
505 }
506 return result;
6ab3bc95 507} // eo read_file(const std::string&)
6a93d84a
TJ
508
509
510/**
511 * @brief writes a string to a file.
512 * @param path path to the file
513 * @param data the data which should be written into the file
71f7bfb8 514 * @param trunc set the trunc flag when opening the file. Do not use for files in /proc and /sys
6a93d84a
TJ
515 * @return @a true if the data was written to the file.
516 *
517 * A simple (q'n'd) function for writing a string to a file.
518 */
71f7bfb8 519bool write_file(const std::string& path, const std::string& data, bool trunc)
6a93d84a 520{
71f7bfb8
GE
521 // set the correct openmode flags
522 std::ios_base::openmode flags = std::ios::out | std::ios::binary;
523 if (trunc)
524 flags |= std::ios::trunc;
525
526 std::ofstream f( path.c_str(), flags);
6a93d84a
TJ
527 if (f.good())
528 {
529 f.write( data.data(), data.size() );
530 return f.good();
531 }
e93545dd 532 else
6a93d84a 533 {
e93545dd 534 return false;
6a93d84a 535 }
6ab3bc95 536} // eo write_file(const std::string&,const std::string&)
6a93d84a
TJ
537
538
539/**
1a2fc4e8
TJ
540 * Copy file in 4k blocks from source to target.
541 * Overwrites the target if it already exists.
542 *
543 * On error the target file gets removed.
544 *
545 * @param src source file
546 * @param dest target file
547 * @return true if all is ok, false on error
548 */
549bool copy_file(const std::string& src, const std::string& dest)
550{
a1e7d2a7
GE
551 std::ifstream input( src.c_str(), std::ios::in | std::ios::binary );
552 if (!input)
553 return false;
554
555 std::ofstream output( dest.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
556 if (!output)
557 return false;
558
559 // Out of disc space?
560 if (!copy_stream(input,output))
561 {
562 output.close();
563 unlink(dest);
564 return false;
565 }
566
567 return true;
568}
569
570/**
571 * Copy streams in 4k blocks.
572 *
573 * @param is source stream
574 * @param os target stream
575 * @return true if all is ok, false on error
576 */
577bool copy_stream(std::istream& is, std::ostream& os)
578{
579 if (!is)
1a2fc4e8
TJ
580 return false;
581
a1e7d2a7 582 if (!os)
1a2fc4e8
TJ
583 return false;
584
585 char buffer[4096];
a1e7d2a7 586 while (is.good())
1a2fc4e8 587 {
a1e7d2a7
GE
588 is.read(buffer, sizeof(buffer));
589 os.write(buffer, is.gcount());
590
591 // Can't write?
592 if (!os.good())
1a2fc4e8 593 return false;
1a2fc4e8
TJ
594 }
595
596 return true;
597}
598
599/**
6a93d84a
TJ
600 * @brief returns the filename part of a path (last component)
601 * @param path the path.
602 * @return the last component of the path.
603 */
604std::string basename(const std::string& path)
605{
606 std::string::size_type pos= path.rfind('/');
607 if (pos != std::string::npos)
608 {
609 return path.substr(pos+1);
610 }
611 return path;
612} // eo basename(const std::string&)
613
614
615/**
616 * @brief returns the directory part of a path.
617 * @param path the path.
618 * @return the directory part of the path.
619 */
620std::string dirname(const std::string& path)
621{
622 std::string::size_type pos= path.rfind('/');
623 if (pos != std::string::npos)
624 {
625 std::string result(path,0,pos);
626 if (result.empty())
627 {
628 return ".";
629 }
630 return result;
631 }
632 return ".";
633} // eo dirname(const std::string&)
634
635
636/**
637 * @brief normalizes a path.
638 *
639 * This method removes empty and "." elements.
640 * It also resolves ".." parts by removing previous path elements if possible.
641 * Leading ".." elements are preserved when a relative path was given; else they are removed.
642 * Trailing slashes are removed.
643 *
644 * @param path the path which should be normalized.
645 * @return the normalized path.
646 */
647
a287a306 648std::string normalize_path(const std::string& path)
6a93d84a
TJ
649{
650 if (path.empty())
651 {
652 return std::string();
653 }
654 // remember if the given path was absolute since this information vanishes when
655 // we split the path (since we split with omitting empty parts...)
656 bool is_absolute= (path[0]=='/');
657 std::list< std::string > parts;
658 std::list< std::string > result_parts;
659
6ab3bc95 660 split_string(path,parts,"/",true);
6a93d84a
TJ
661
662 for(std::list< std::string >::const_iterator it_parts= parts.begin();
663 it_parts != parts.end();
664 ++it_parts)
665 {
666 std::string part(*it_parts); //convenience..
667 if (part == std::string(".") )
668 {
669 // single dot is "current path"; ignore!
670 continue;
671 }
672 if (part == std::string("..") )
673 {
674 // double dot is "one part back"
675 if (result_parts.empty())
676 {
677 if (is_absolute)
678 {
679 // ignore since we cannot move behind / on absolute paths...
680 }
681 else
682 {
683 // on relative path, we need to store the "..":
684 result_parts.push_back(part);
685 }
686 }
687 else if (result_parts.back() == std::string("..") )
688 {
689 // if last element was already "..", we need to store the new one again...
690 // (PS: no need for "absolute" check; this can only be the case on relative path)
691 result_parts.push_back(part);
692 }
693 else
694 {
695 // remove last element.
696 result_parts.pop_back();
697 }
698 continue;
699 }
700 result_parts.push_back(part);
701 }
702 std::string result;
703 if (is_absolute)
704 {
705 result= "/";
706 }
6ab3bc95 707 result+= join_string(result_parts,"/");
6a93d84a 708 return result;
6ab3bc95 709} // eo normalize_path(const std::string&)
6a93d84a
TJ
710
711
712/**
f002679a
GE
713 * @brief calls fsync on a given directory to sync all it's metadata
714 * @param path the path of the directory.
715 * @return true if successful
716 */
717bool dirsync(const std::string& path)
718{
719 // sync the directory the file is in
720 DIR* dir=opendir(path.c_str());
721 if (dir == NULL)
722 return false;
723
724 int ret=fsync(dirfd(dir));
725
726 closedir(dir);
727
728 return (ret==0);
729}
730
731/**
6a93d84a
TJ
732 * @brief changes the file(/path) mode.
733 * @param path the path to change the mode for.
734 * @param mode the new file mode.
735 * @return @a true iff the file mode was sucessfully changed.
736 */
737bool chmod(const std::string& path, int mode)
738{
739 int res= ::chmod(path.c_str(), mode);
740 return (res==0);
741} // eo chmod(const std::string&,int)
742
743
744/**
745 * @brief changed the owner of a file(/path)
746 * @param path the path to change the owner for.
747 * @param user the new file owner.
748 * @param group the new file group.
749 * @return @a true iff the file owner was succesfully changed.
750 *
751 * @note
752 * the validity of user and group within the system is not checked.
753 * This is intentional since this way we can use id's which are not assigned.
754 */
a287a306 755bool chown(const std::string& path, const I2n::User& user, const I2n::Group& group)
6a93d84a 756{
a287a306 757 uid_t uid= user.Uid;
6a93d84a 758 if (uid<0) return false;
a287a306
TJ
759 gid_t gid= group.Gid;
760 if (gid<0) gid= user.Gid;
6a93d84a
TJ
761 if (gid<0) return false;
762 int res= ::chown( path.c_str(), uid, gid);
763 return (res==0);
764} // eo chown(const std::string&,const User&,const Group&)
e93545dd 765
0a654ec0
TJ
766/**
767 * Recursive delete of files and directories
768 * @param path File or directory to delete
997987a6 769 * @param keep_parent_dir Keep parent directory (=empty out directory) [optional]
44725532 770 * @param error Will contain the error if the return value is false [optional]
0a654ec0
TJ
771 * @return true on success, false otherwise
772 */
997987a6
TJ
773bool recursive_delete(const std::string &path,
774 bool keep_parent_dir,
775 std::string *error)
0a654ec0
TJ
776{
777 bool rtn = true;
778
854d7ee8
GE
779 try
780 {
9bee14ce
GE
781 Stat sp(path, false);
782 if (!sp)
0a654ec0 783 throw runtime_error("can't stat " + path);
0a654ec0 784
9bee14ce 785 if (sp.is_directory())
854d7ee8 786 {
8decfb5a
GE
787 std::vector<std::string> dirents = get_dir(path, false);
788 BOOST_FOREACH(const std::string &filename, dirents)
10c3af2d 789 {
0a654ec0 790 // Delete subdir or file.
997987a6 791 rtn = recursive_delete(path + "/" + filename, false, error);
854d7ee8 792 if (rtn == false)
0a654ec0 793 break;
0a654ec0
TJ
794 }
795
854d7ee8 796 if (keep_parent_dir == false && !rmdir(path))
0a654ec0 797 throw runtime_error("can't remove directory " + path);
854d7ee8
GE
798 }
799 else
800 {
801 if (!unlink(path))
0a654ec0 802 throw runtime_error("can't unlink " + path);
0a654ec0 803 }
854d7ee8
GE
804 }
805 catch (exception &e)
806 {
807 if (error)
808 {
0a654ec0
TJ
809 ostringstream out;
810 out << e.what() << " (" << strerror(errno) << ")";
811 *error = out.str();
812 }
813 rtn = false;
854d7ee8
GE
814 }
815 catch (...)
816 {
817 if (error)
818 {
0a654ec0
TJ
819 ostringstream out;
820 out << "unknown error (" << strerror(errno) << ")";
821 *error = out.str();
822 }
823 rtn = false;
824 }
e93545dd 825
0a654ec0 826 return rtn;
6ab3bc95 827} // eo recursive_delete(const std::string&,std::string*)
6a93d84a 828
faf8475b
TJ
829/**
830 Create a unique temporary directory from path_template.
831 @param Path template. The last six characters must be XXXXXX.
312b9bb0 832 @param error Will contain the error if the return value is empty [optional]
faf8475b 833 @return Name of new directory or empty string on error.
9513f841 834
312b9bb0 835 @seealso: classes in tmpfstream which offer functionality based on mkstemp
faf8475b 836*/
44725532 837std::string mkdtemp(const std::string &path_template, std::string *error)
faf8475b
TJ
838{
839 boost::scoped_array<char> buf( new char[path_template.size()+1] );
840 path_template.copy(buf.get(), path_template.size());
841 buf[path_template.size()]=0;
842
843 char *unique_dir = ::mkdtemp(buf.get());
844 if (!unique_dir)
44725532
TJ
845 {
846 if (error)
847 *error = strerror(errno);
faf8475b 848 return "";
44725532 849 }
faf8475b
TJ
850
851 // Scoped pointer is still valid
852 return std::string(unique_dir);
853}
854
44725532
TJ
855/**
856 Create directory
857 @param path Path to create
858 @param error Will contain the error if the return value is false [optional]
859 @return True on success, false on error
860*/
861bool mkdir(const std::string &path, const mode_t &mode, std::string *error)
862{
863 if ( ::mkdir(path.c_str(), mode) == 0)
864 return true;
865
866 if (error)
867 *error = strerror(errno);
868 return false;
869}
870
871/**
872 Remove directory
873 @param path Path to removed
874 @param error Will contain the error if the return value is false [optional]
875 @return True on successs, false otherwise
876*/
877bool rmdir(const std::string &path, std::string *error)
878{
879 if ( ::rmdir(path.c_str() ) == 0)
880 return true;
881
882 if (error)
883 *error = strerror(errno);
884 return false;
885}
886
887/// Small helper class for scoped free
888class scoped_C_free
889{
890public:
891 scoped_C_free(void *ptr)
892 : pointer_to_free(ptr)
893 {
894 }
895
896 ~scoped_C_free()
897 {
898 free (pointer_to_free);
899 pointer_to_free = NULL;
900 }
901
902private:
903 void *pointer_to_free;
904};
905
906/**
907 Get current working directory
908 @return Current working directory. Empty string on error.
909*/
910std::string getcwd()
911{
912 char *cwd = ::getcwd(NULL, 0);
913 if (!cwd)
914 return "";
915
916 // Make deallocation of cwd exception safe
917 scoped_C_free holder(cwd);
918
919 string current_dir(cwd);
920 return current_dir;
921}
922
923/**
924 Change current working directory
925 @param path Path to change to
926 @param error Will contain the error if the return value is false [optional]
927 @return True on successs, false otherwise
928*/
929bool chdir(const std::string &path, std::string *error)
930{
931 if ( ::chdir(path.c_str() ) == 0)
932 return true;
933
934 if (error)
935 *error = strerror(errno);
936 return false;
937}
faf8475b 938
901e2943
TJ
939/**
940 Set file mode creation mask
941 @param mask Creation mask
942 @return Previous creation mask (function call always succeeds)
943*/
944mode_t umask(mode_t mask)
945{
946 return ::umask(mask);
947}
948
deab8f59
CH
949
950/**
951 * @brief Remove unlisted files
952 *
46f8c9ca 953 * @param directory Directory to look for files
deab8f59
CH
954 * @param keep_files List of files or directories to keep
955 * @param prefix Filename prefix to match. Empty prefix matches all.
956 *
957 * @return bool True if the directory was scanned, false on error (directory not found, permission denied)
958 **/
959bool remove_unlisted_files(const std::string &directory,
960 const std::set<std::string> &keep_files,
961 const std::string &prefix)
962{
963 std::vector<std::string> content;
964 if (!get_dir(directory, content, false))
965 return false;
966
967 bool all_fine = true;
968 BOOST_FOREACH(const std::string &file, content)
969 {
970 // Check for filename prefix (if any)
971 if (!prefix.empty() && file.find(prefix) != 0)
972 continue;
973
974 // Check if file is whitelisted
975 if (keep_files.find(file) != keep_files.end())
976 continue;
977
978 // Try to unlink file. (Continue on error)
979 if (!unlink(directory + "/" + file))
980 all_fine = false;
981 }
982
983 return all_fine;
984}
985
34ed5e5e
GE
986/**
987 * @brief Get free size in bytes on a given path or filename
988 *
989 * @param path Directory or filename to look in
990 *
991 * @return Number of bytes available to a regular user, -1 in case of an error
992 **/
993long long get_free_diskspace(const std::string& path)
994{
995 struct statvfs sf;
996
078c214d 997 int looplimit=10000;
34ed5e5e 998 int ret;
078c214d
GE
999 while ( ((ret=statvfs(path.c_str(),&sf)) == -1) && (errno==EINTR) && looplimit > 0)
1000 looplimit--;
34ed5e5e
GE
1001
1002 if (ret==-1)
1003 {
1004 // a real error occured
1005 return -1;
1006 }
1007
1008 long long free_bytes=0;
1009
1010 // load block size
1011 free_bytes=sf.f_bsize;
1012
1013 // multiply by number of free blocks accessible by normal users
1014 // make sure we really multiply long long by long long and don't overflow at 2 GB
1015 free_bytes*=(long long)sf.f_bavail;
1016
1017 return free_bytes;
1018}
1019
74311678
GE
1020namespace
1021{
1022// anonymous namespace to make du_internal inaccessible from outside
1023
1024// internally used by du, do not use for other things
1025void du_internal(const std::string &path, long long &sum, std::map<dev_t, std::set<ino_t> > &counted_inodes)
1026{
1027
1028 Stat sp(path, false); // don't dereference symlinks here
1029 if (!sp)
1030 throw runtime_error("can't stat " + path);
1031
1032 // make sure we don't count hardlinked files twice
1033 bool count_file=true;
1034
1035 // dirs can't be hardlinked, their nlink is the size of entries -> doesn't matter for us here
1036 if (!sp.is_directory() && sp.nlink() > 1)
1037 {
1038 // see if we have remembered this dev / inode combination
1039 if (counted_inodes[sp.device()].count(sp.inode()))
1040 count_file=false;
1041 else
1042 counted_inodes[sp.device()].insert(sp.inode());
1043 }
1044
1045 // always add the space used, even if we have a directory, symlink or whatever:
1046 // they need space on disk too
1047
1048 if (count_file)
1049 sum+=sp.bytes_on_disk();
1050
1051 if (sp.is_directory())
1052 {
1053 std::vector<std::string> dirents = get_dir(path, false);
1054 BOOST_FOREACH(const std::string &filename, dirents)
1055 {
1056 // calculate size of subdir or file
1057 du_internal(path + "/" + filename, sum, counted_inodes);
1058 }
1059 }
1060}
1061
1062} // eo anon namespace
1063
fcadd7ba 1064/**
74311678 1065 * like du(1): return the number bytes used by a directory structure, counting hardlinked files only once
fcadd7ba
GE
1066 * @param path File or directory to start counting recursively
1067 * @param error Will contain the error if the return value is -1 [optional]
1068 * @return size in bytes on success, -1 on error
fcadd7ba
GE
1069 */
1070long long du(const std::string &path, std::string *error)
1071{
1072 long long sum = 0;
1073
74311678
GE
1074 std::map<dev_t, std::set<ino_t> > counted_inodes;
1075
fcadd7ba
GE
1076 try
1077 {
74311678 1078 du_internal(path, sum, counted_inodes);
fcadd7ba
GE
1079 }
1080 catch (exception &e)
1081 {
1082 if (error)
1083 {
1084 ostringstream out;
1085 out << e.what() << " (" << strerror(errno) << ")";
1086 *error = out.str();
1087 }
1088 return -1;
1089 }
1090 catch (...)
1091 {
1092 if (error)
1093 {
1094 ostringstream out;
1095 out << "unknown error (" << strerror(errno) << ")";
1096 *error = out.str();
1097 }
1098 return -1;
1099 }
1100
1101 return sum;
1102} // eo du
1103
deab8f59 1104
6ab3bc95 1105} // eo namespace I2n