MVE - Multi-View Environment mve-devel
Loading...
Searching...
No Matches
file_system.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 2015, Simon Fuhrmann, Andre Schulz
3 * TU Darmstadt - Graphics, Capture and Massively Parallel Computing
4 * All rights reserved.
5 *
6 * This software may be modified and distributed under the terms
7 * of the BSD 3-Clause license. See the LICENSE.txt file for details.
8 */
9
10#include <iostream>
11#include <fstream>
12#include <stdexcept>
13#include <cctype>
14#include <cerrno> // errno
15#include <cstring> // std::strerror
16#include <cstdio> // std::rename
17#include <algorithm>
18
19#if defined(_WIN32)
20# include <direct.h>
21# include <io.h>
22# include <shlobj.h>
23# include <sys/stat.h>
24# include <sys/types.h>
25#else // Linux, OSX, ...
26# include <dirent.h>
27# include <unistd.h>
28# include <sys/stat.h>
29# include <sys/types.h>
30# include <pwd.h>
31#endif
32
33#if defined(__APPLE__)
34# include <mach-o/dyld.h> // for _NSGetExecutablePath
35#endif
36
37#ifndef PATH_MAX
38# define PATH_MAX 2048
39#endif
40
41#include "util/exception.h"
42#include "util/system.h"
43#include "util/file_system.h"
44
47
48// PATH_MAX might be long?
49char home_path[PATH_MAX] = { 0 };
50char app_data_path[PATH_MAX] = { 0 };
51
52bool
53exists (char const* pathname)
54{
55#ifdef _WIN32
56 struct _stat statbuf;
57 if (::_stat(pathname, &statbuf) < 0)
58 return false;
59#else // _WIN32
60 struct stat statbuf;
61 if (::stat(pathname, &statbuf) < 0)
62 return false;
63#endif // _WIN32
64
65 return true;
66}
67
68/* ---------------------------------------------------------------- */
69
70bool
71dir_exists (char const* pathname)
72{
73#ifdef _WIN32
74 struct _stat statbuf;
75 if (::_stat(pathname, &statbuf) < 0)
76 return false;
77
78 if (!(statbuf.st_mode & _S_IFDIR))
79 return false;
80#else // _WIN32
81 struct stat statbuf;
82 if (::stat(pathname, &statbuf) < 0)
83 return false;
84
85 if (!S_ISDIR(statbuf.st_mode))
86 return false;
87#endif // _WIN32
88
89 return true;
90}
91
92/* ---------------------------------------------------------------- */
93
94bool
95file_exists (char const* pathname)
96{
97#ifdef _WIN32
98 struct _stat statbuf;
99 if (::_stat(pathname, &statbuf) < 0)
100 return false;
101
102 if (!(statbuf.st_mode & _S_IFREG))
103 return false;
104#else // _WIN32
105 struct stat statbuf;
106 if (::stat(pathname, &statbuf) < 0)
107 return false;
108
109 if (!S_ISREG(statbuf.st_mode))
110 return false;
111#endif // _WIN32
112
113 return true;
114}
115
116/* ---------------------------------------------------------------- */
117
118char*
119get_cwd (char* buf, size_t size)
120{
121#ifdef _WIN32
122 return ::_getcwd(buf, size);
123#else // _WIN32
124 return ::getcwd(buf, size);
125#endif // _WIN32
126}
127
128/* ---------------------------------------------------------------- */
129
130bool
131set_cwd (char const* pathname)
132{
133#ifdef _WIN32
134 return ::_chdir(pathname) >= 0;
135#else // _WIN32
136 return ::chdir(pathname) >= 0;
137#endif // _WIN32
138}
139
140/* ---------------------------------------------------------------- */
141
142bool
143mkdir (char const* pathname/*, mode_t mode*/)
144{
145#ifdef _WIN32
146 if (::_mkdir(pathname) < 0)
147 return false;
148#else // _WIN32
149 if (::mkdir(pathname, S_IRWXU | S_IRGRP | S_IXGRP) < 0)
150 return false;
151#endif // _WIN32
152
153 return true;
154}
155
156/* ---------------------------------------------------------------- */
157
158bool
159rmdir (char const* pathname)
160{
161#ifdef _WIN32
162 return ::_rmdir(pathname) >= 0;
163#else // _WIN32
164 return ::rmdir(pathname) >= 0;
165#endif // _WIN32
166}
167/* ---------------------------------------------------------------- */
168
169bool
170unlink (char const* pathname)
171{
172#ifdef _WIN32
173 return ::_unlink(pathname) >= 0;
174#else // _WIN32
175 return ::unlink(pathname) >= 0;
176#endif // _WIN32
177}
178
179/* ---------------------------------------------------------------- */
180
181char const*
183{
184 if (*app_data_path != 0)
185 return app_data_path;
186
187 // TODO: Use environment variables?
188
189#ifdef _WIN32
190 // SHGetFolderPathA seems to expect non-wide chars
191 // http://msdn.microsoft.com/en-us/library/bb762181(VS.85).aspx
192 // FIXME: Max length of home path?
193 if (!SUCCEEDED(::SHGetFolderPathA(0, CSIDL_APPDATA, 0, 0, app_data_path)))
194 throw util::Exception("Cannot determine home directory");
195#else // _WIN32
196 std::string p = join_path(get_home_dir(), ".local/share");
197 if (p.size() >= PATH_MAX)
198 throw util::Exception("Cannot determine home directory");
199 std::strncpy(app_data_path, p.c_str(), PATH_MAX - 1);
200 app_data_path[PATH_MAX - 1] = '\0';
201#endif // _WIN32
202
203 return app_data_path;
204}
205
206/* ---------------------------------------------------------------- */
207
208char const*
210{
211 if (*home_path != 0)
212 return home_path;
213
214 // TODO: Use HOME environment variable?
215
216#ifdef _WIN32
217 // SHGetFolderPathA seems to expect non-wide chars
218 // http://msdn.microsoft.com/en-us/library/bb762181(VS.85).aspx
219 // FIXME: Max length of home path?
220 if (!SUCCEEDED(::SHGetFolderPathA(0, CSIDL_APPDATA, 0, 0, home_path)))
221 throw util::Exception("Cannot determine home directory");
222#else // _WIN32
223 uid_t user_id = ::geteuid();
224 struct passwd* user_info = ::getpwuid(user_id);
225 if (user_info == nullptr || user_info->pw_dir == nullptr)
226 throw util::Exception("Cannot determine home directory");
227 std::strncpy(home_path, user_info->pw_dir, PATH_MAX - 1);
228 home_path[PATH_MAX - 1] = '\0';
229#endif // _WIN32
230
231 return home_path;
232}
233
234/* ---------------------------------------------------------------- */
235
236bool
237rename (char const* from, char const* to)
238{
239 if (std::rename(from, to) < 0)
240 return false;
241
242 return true;
243}
244
245/* ---------------------------------------------------------------- */
246
247void
248copy_file (char const* src, char const* dst)
249{
250 std::ifstream src_stream(src, std::ios::binary);
251 if (!src_stream.good())
252 throw util::FileException(src, std::strerror(errno));
253 std::ofstream dst_stream(dst, std::ios::binary);
254 if (!dst_stream.good())
255 throw util::FileException(dst, std::strerror(errno));
256
257 int const BUFFER_SIZE = 4096;
258 char buffer[BUFFER_SIZE];
259 while (!src_stream.eof())
260 {
261 src_stream.read(buffer, BUFFER_SIZE);
262 if (src_stream.bad())
263 throw util::FileException(src, std::strerror(errno));
264 dst_stream.write(buffer, src_stream.gcount());
265 if (!dst_stream.good())
266 throw util::FileException(dst, std::strerror(errno));
267 }
268
269 src_stream.close();
270 if (src_stream.bad())
271 throw util::FileException(src, std::strerror(errno));
272 dst_stream.close();
273 if (!dst_stream.good())
274 throw util::FileException(dst, std::strerror(errno));
275}
276
277/* ---------------------------------------------------------------- */
278
279std::string
281{
282 std::size_t size = 1 << 8;
283 while (true)
284 {
285 char* buf = new char[size];
286 if (get_cwd(buf, size) == buf)
287 {
288 std::string ret(buf);
289 delete[] buf;
290 return ret;
291 }
292 delete[] buf;
293 size = size << 1;
294 if (size > (1 << 15))
295 throw Exception("Error storing CWD");
296 }
297}
298
299/* ---------------------------------------------------------------- */
300// TODO Test on Win32 and OSX
301
302std::string
304{
305 char path[PATH_MAX];
306 std::fill(path, path + PATH_MAX, '\0');
307
308#if defined(_WIN32)
309
310# ifdef UNICODE
311# error "Unicode defined but not supported"
312# endif
313
314 TCHAR omgwtf[PATH_MAX];
315 int n_chars = GetModuleFileName(nullptr, omgwtf, PATH_MAX);
316 std::copy(omgwtf, omgwtf + n_chars, path);
317
318#elif defined(__APPLE__)
319
320 /*
321 * http://developer.apple.com/library/mac/#documentation/
322 * ... Darwin/Reference/ManPages/man3/dyld.3.html
323 */
324 uint32_t pathmax = PATH_MAX;
325 int success = _NSGetExecutablePath(path, &pathmax);
326 int n_chars = 0;
327 if (success < 0)
328 throw std::runtime_error(
329 "Could not determine binary path: _NSGetExecutablePath failed!");
330 else
331 {
332 char real[PATH_MAX];
333 if (::realpath(path, real) == nullptr)
334 throw std::runtime_error(
335 "Could not determine binary path: realpath failed!");
336 ::strncpy(path, real, PATH_MAX);
337 }
338
339#elif defined(__linux) || defined(__CYGWIN__)
340
341 ssize_t n_chars = ::readlink("/proc/self/exe", path, PATH_MAX);
342
343#else
344# error "Cannot determine binary path: Unsupported OS"
345#endif
346
347 if (n_chars >= PATH_MAX)
348 throw std::runtime_error("Buffer size too small!");
349
350 return std::string(path);
351}
352
353/* ---------------------------------------------------------------- */
354
355bool
356is_absolute (std::string const& path)
357{
358#ifdef _WIN32
359 return path.size() >= 2 && std::isalpha(path[0]) && path[1] == ':';
360#else
361 return path.size() >= 1 && path[0] == '/';
362#endif
363}
364
365std::string
366sanitize_path (std::string const& path)
367{
368 if (path.empty())
369 return "";
370
371 std::string result = path;
372
373 /* Replace backslashes with slashes. */
374 std::replace(result.begin(), result.end(), '\\', '/');
375
376 /* Remove double slashes. */
377 for (std::size_t i = 0; i < result.size() - 1; )
378 {
379 if (result[i] == '/' && result[i + 1] == '/')
380 result.erase(i, 1);
381 else
382 i += 1;
383 }
384
385 /* Remove trailing slash if result != "/". */
386 if (result.size() > 1 && result[result.size() - 1] == '/')
387 result.erase(result.end() - 1);
388
389 return result;
390}
391
392std::string
393join_path (std::string const& path1, std::string const& path2)
394{
395 std::string p2 = sanitize_path(path2);
396 if (is_absolute(p2))
397 return p2;
398
399#ifdef _WIN32
400 if (!p2.empty() && p2[0] == '/')
401 return sanitize_path(path1) + p2;
402#endif
403
404 return sanitize_path(path1) + '/' + p2;
405}
406
407std::string
408abspath (std::string const& path)
409{
410 return join_path(get_cwd_string(), path);
411}
412
413std::string
414dirname (std::string const& path)
415{
416 if (path.empty())
417 return ".";
418
419 /* Skip group of slashes at the end. */
420 std::size_t len = path.size();
421 while (len > 0 && path[len - 1] == '/')
422 len -= 1;
423 if (len == 0)
424 return "/";
425
426 /* Skip basename. */
427 while (len > 0 && path[len - 1] != '/')
428 len -= 1;
429 if (len == 0)
430 return ".";
431
432 /* Skip group of slashes. */
433 while (len > 1 && path[len - 1] == '/')
434 len -= 1;
435
436 return path.substr(0, len);
437}
438
439std::string
440basename (std::string const& path)
441{
442 /* Skip group of slashes at the end. */
443 std::size_t len = path.size();
444 while (len > 0 && path[len - 1] == '/')
445 len -= 1;
446 if (len == 0)
447 return "";
448
449 /* Skip basename. */
450 std::size_t base = len - 1;
451 while (base > 0 && path[base - 1] != '/')
452 base -= 1;
453
454 return path.substr(base, len - base);
455}
456
457/* ---------------------------------------------------------------- */
458
459std::string
460replace_extension (std::string const& fn, std::string const& ext)
461{
462 std::size_t slashpos = fn.find_last_of('/');
463 if (slashpos == std::string::npos)
464 slashpos = 0;
465
466 std::size_t dotpos = fn.find_last_of('.');
467 if (dotpos == std::string::npos || dotpos < slashpos)
468 return fn + "." + ext;
469
470 return fn.substr(0, dotpos) + "." + ext;
471}
472
473/*
474 * ----------------------------- File IO ----------------------------
475 */
476
477void
478read_file_to_string (std::string const& filename, std::string* data)
479{
480 std::ifstream in(filename.c_str(), std::ios::binary);
481 if (!in.good())
482 throw util::FileException(filename, std::strerror(errno));
483 in.seekg(0, std::ios::end);
484 std::size_t length = in.tellg();
485 in.seekg(0, std::ios::beg);
486 data->resize(length);
487 in.read(&(*data)[0], length);
488 in.close();
489}
490
491void
492write_string_to_file (std::string const& data, std::string const& filename)
493{
494 write_string_to_file(&data[0], data.size(), filename);
495}
496
497void
498write_string_to_file (char const* data, std::size_t len,
499 std::string const& filename)
500{
501 std::ofstream out(filename.c_str(), std::ios::binary);
502 if (!out.good())
503 throw util::FileException(filename, std::strerror(errno));
504 out.write(data, len);
505 out.close();
506}
507
510
511/* ----------------------------------------------------------------
512 * ----------------- File and Directory abstraction ---------------
513 * ---------------------------------------------------------------- */
514
517
518std::string
519File::get_absolute_name (void) const
520{
521#ifdef _WIN32
522 // FIXME: Use '/' even on windows?
523 return (!path.empty() && *path.rbegin() == '\\'
524 ? path + name
525 : path + "\\" + name);
526#else
527 return (!path.empty() && *path.rbegin() == '/'
528 ? path + name
529 : path + "/" + name);
530#endif
531}
532
533/* ---------------------------------------------------------------- */
534
535bool
536File::operator< (File const& rhs) const
537{
538 if (this->is_dir && !rhs.is_dir)
539 return true;
540 else if (!this->is_dir && rhs.is_dir)
541 return false;
542 else if (this->path < rhs.path)
543 return true;
544 else if (rhs.path < this->path)
545 return false;
546 else
547 return (this->name < rhs.name);
548}
549
550/* ---------------------------------------------------------------- */
551
552void
553Directory::scan (std::string const& path)
554{
555 this->clear();
556
557#ifdef _WIN32
558 WIN32_FIND_DATA data;
559 HANDLE hf = FindFirstFile((path + "/*").c_str(), &data);
560 if (hf == INVALID_HANDLE_VALUE)
561 throw Exception("Cannot open directory");
562
563 do
564 {
565 if (!std::strcmp(data.cFileName, "."))
566 continue;
567 if (!std::strcmp(data.cFileName, ".."))
568 continue;
569
570 this->push_back(File());
571 this->back().path = sanitize_path(path);
572 this->back().name = data.cFileName;
573 this->back().is_dir =
574 (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
575 }
576 while (FindNextFile(hf, &data) != 0);
577
578 FindClose(hf);
579#else
580 DIR *dp = ::opendir(path.c_str());
581 if (dp == nullptr)
582 throw Exception("Cannot open directory: ", std::strerror(errno));
583
584 struct dirent *ep;
585 while ((ep = ::readdir(dp)))
586 {
587 if (!std::strcmp(ep->d_name, "."))
588 continue;
589 if (!std::strcmp(ep->d_name, ".."))
590 continue;
591 this->push_back(File());
592 this->back().path = path;
593 this->back().name = ep->d_name;
594 this->back().is_dir = (ep->d_type == DT_DIR);
595 if (ep->d_type == DT_UNKNOWN)
596 {
597 struct stat path_stat;
598 if (::stat(join_path(path, ep->d_name).c_str(), &path_stat) >= 0)
599 this->back().is_dir = S_ISDIR(path_stat.st_mode);
600 }
601 }
602 ::closedir(dp);
603#endif
604}
605
606/*
607 * --------------------- File locking mechanism ----------------------
608 */
609
610FileLock::FileLock (std::string const& filename)
611{
612 switch (this->acquire_retry(filename))
613 {
614 case LOCK_CREATED: break;
615 default: throw util::Exception(this->reason);
616 }
617}
618
620FileLock::acquire (std::string const& filename)
621{
622 /* Check if lock file exists. */
623 this->lockfile = filename + ".lock";
624 this->reason.clear();
625 if (fs::file_exists(this->lockfile.c_str()))
626 {
627 this->reason = "Previous lock existing";
628 return FileLock::LOCK_EXISTS;
629 }
630
631 /* Finally create the lock file. */
632 std::ofstream touch(this->lockfile.c_str(), std::ios::binary);
633 if (!touch.good())
634 {
635 this->reason = "Error locking: ";
636 this->reason += std::strerror(errno);
637 return FileLock::LOCK_CREATE_ERROR;
638 }
639 touch.close();
640
641 /* Return success, lock is created. */
642 return FileLock::LOCK_CREATED;
643}
644
646FileLock::acquire_retry (std::string const& filename, int retries, int sleep)
647{
648 /* Try to acquire file lock for 'retry' times. */
649 while (retries > 0)
650 {
651 Status status = this->acquire(filename);
652 if (status != FileLock::LOCK_EXISTS)
653 return status;
654
655 system::sleep(sleep);
656 retries -= 1;
657
658 /* Fail if all retries have been used. */
659 if (retries <= 0)
660 {
661 this->reason = "Previous lock persisting";
662 return FileLock::LOCK_PERSISTENT;
663 }
664 }
665
666 /* Return success, lock is created. */
667 return FileLock::LOCK_CREATED;
668}
669
670bool
671FileLock::is_locked (std::string const& filename)
672{
673 std::string lockfname = filename + ".lock";
674 return fs::file_exists(lockfname.c_str());
675}
676
677bool
678FileLock::wait_lock (std::string const& filename, int retries, int sleep)
679{
680 while (retries > 0 && this->is_locked(filename))
681 {
682 system::sleep(sleep);
683 retries -= 1;
684 if (retries <= 0)
685 return false;
686 }
687 return true;
688}
689
690bool
691FileLock::release (void)
692{
693 if (this->lockfile.empty())
694 return false;
695 return fs::unlink(this->lockfile.c_str());
696}
697
Universal, simple exception class.
Definition exception.h:24
Exception class for file exceptions with additional filename.
Definition exception.h:53
#define PATH_MAX
std::string replace_extension(std::string const &fn, std::string const &ext)
Replaces extension of the given file with 'ext'.
bool unlink(char const *pathname)
Unlinks (deletes) the given file.
std::string sanitize_path(std::string const &path)
Canonicalize slashes in the given path.
std::string abspath(std::string const &path)
Returns the absolute representation of the given path.
bool is_absolute(std::string const &path)
Checks whether the given path is absolute.
char * get_cwd(char *buf, size_t size)
bool mkdir(char const *pathname)
Creates a new directory.
void write_string_to_file(std::string const &data, std::string const &filename)
Writes the given data into a file.
bool rmdir(char const *pathname)
Removes an empty directory.
char app_data_path[2048]
void copy_file(char const *src, char const *dst)
Copies a file from 'src' to 'dst', throws FileException on error.
bool exists(char const *pathname)
Determines if the given path is a directory.
std::string get_binary_path(void)
Returns the path of the binary currently executing.
std::string dirname(std::string const &path)
Returns the directory name component of the given path.
bool dir_exists(char const *pathname)
Determines if the given path is a directory.
char const * get_app_data_dir(void)
Determines the current user's path for application data.
char home_path[2048]
bool rename(char const *from, char const *to)
Renames the given file 'from' to new name 'to'.
bool set_cwd(char const *pathname)
Changes the current working directory to 'pathname' and returns true on success.
std::string join_path(std::string const &path1, std::string const &path2)
Concatenate and canonicalize two paths.
bool file_exists(char const *pathname)
Determines if the given path is a file.
std::string basename(std::string const &path)
Returns the file name component of the given path.
void read_file_to_string(std::string const &filename, std::string *data)
Reads the whole file into a string.
char const * get_home_dir(void)
Determines the home path for the current user.
std::string get_cwd_string(void)
Determines the CWD and returns a convenient string.
std::string path
std::string name
#define UTIL_NAMESPACE_BEGIN
Definition defines.h:13
#define UTIL_NAMESPACE_END
Definition defines.h:14
#define UTIL_FS_NAMESPACE_BEGIN
Definition defines.h:16
#define UTIL_FS_NAMESPACE_END
Definition defines.h:17