Add PBD::get_directory_contents to pbd/file_utils.h
[ardour.git] / libs / pbd / file_utils.cc
1 /*
2     Copyright (C) 2007-2014 Tim Mayberry
3     Copyright (C) 1998-2014 Paul Davis
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <algorithm>
22 #include <vector>
23
24 #include <glib.h>
25 #include <glib/gstdio.h>
26
27 #ifdef COMPILER_MINGW
28 #include <io.h> // For W_OK
29 #define strtok_r strtok_s
30 #endif
31
32 #include <glibmm/fileutils.h>
33 #include <glibmm/miscutils.h>
34 #include <glibmm/pattern.h>
35
36 #include <errno.h>
37 #include <string.h> /* strerror */
38
39 /* open() */
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <fcntl.h>
43
44 /* close(), read(), write() */
45 #ifdef COMPILER_MSVC
46 #include <io.h> // Microsoft's nearest equivalent to <unistd.h>
47 #include <ardourext/misc.h>
48 #else
49 #include <dirent.h>
50 #include <unistd.h>
51 #include <regex.h>
52 #endif
53
54 #include "pbd/compose.h"
55 #include "pbd/file_utils.h"
56 #include "pbd/debug.h"
57 #include "pbd/error.h"
58 #include "pbd/pathexpand.h"
59 #include "pbd/stl_delete.h"
60
61 #include "i18n.h"
62
63 using namespace std;
64
65 namespace PBD {
66
67 void
68 get_directory_contents (const std::string& directory_path,
69                        vector<string>& result,
70                        bool files_only,
71                        bool recurse)
72 {
73         // perhaps we don't need this check assuming an exception is thrown
74         // as it would save checking that the path is a directory twice when
75         // recursing
76         if (!Glib::file_test (directory_path, Glib::FILE_TEST_IS_DIR)) return;
77
78         try
79         {
80                 Glib::Dir dir(directory_path);
81                 Glib::DirIterator i = dir.begin();
82
83                 while (i != dir.end()) {
84
85                         string fullpath = Glib::build_filename (directory_path, *i);
86
87                         bool is_dir = Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR);
88
89                         if (is_dir && recurse) {
90                                 get_directory_contents (fullpath, result, files_only, recurse);
91                         }
92
93                         i++;
94
95                         if (is_dir && files_only) {
96                                 continue;
97                         }
98                         result.push_back (fullpath);
99                 }
100         }
101         catch (Glib::FileError& err)
102         {
103                 warning << err.what() << endmsg;
104         }
105 }
106
107 void
108 get_files_in_directory (const std::string& directory_path, vector<string>& result)
109 {
110         if (!Glib::file_test (directory_path, Glib::FILE_TEST_IS_DIR)) return;
111
112         try
113         {
114                 Glib::Dir dir(directory_path);
115                 std::copy(dir.begin(), dir.end(), std::back_inserter(result));
116         }
117         catch (Glib::FileError& err)
118         {
119                 warning << err.what() << endmsg;
120         }
121 }
122
123 void
124 find_matching_files_in_directory (const std::string& directory,
125                                   const Glib::PatternSpec& pattern,
126                                   vector<std::string>& result)
127 {
128         vector<string> tmp_files;
129
130         get_files_in_directory (directory, tmp_files);
131         result.reserve(tmp_files.size());
132
133         for (vector<string>::iterator file_iter = tmp_files.begin();
134                         file_iter != tmp_files.end();
135                         ++file_iter)
136         {
137                 if (!pattern.match(*file_iter)) continue;
138
139                 std::string full_path(directory);
140                 full_path = Glib::build_filename (full_path, *file_iter);
141
142                 DEBUG_TRACE (
143                         DEBUG::FileUtils,
144                         string_compose("Found file %1\n", full_path)
145                         );
146
147                 result.push_back(full_path);
148         }
149 }
150
151 void
152 find_matching_files_in_directories (const vector<std::string>& paths,
153                                     const Glib::PatternSpec& pattern,
154                                     vector<std::string>& result)
155 {
156         for (vector<std::string>::const_iterator path_iter = paths.begin();
157                         path_iter != paths.end();
158                         ++path_iter)
159         {
160                 find_matching_files_in_directory (*path_iter, pattern, result);
161         }               
162 }
163
164 void
165 find_matching_files_in_search_path (const Searchpath& search_path,
166                                     const Glib::PatternSpec& pattern,
167                                     vector<std::string>& result)
168 {
169         find_matching_files_in_directories (search_path, pattern, result);    
170 }
171
172 bool
173 find_file_in_search_path(const Searchpath& search_path,
174                          const string& filename,
175                          std::string& result)
176 {
177         vector<std::string> tmp;
178         Glib::PatternSpec tmp_pattern(filename);
179
180         find_matching_files_in_search_path (search_path, tmp_pattern, tmp);
181
182         if (tmp.size() == 0)
183         {
184                 DEBUG_TRACE (
185                         DEBUG::FileUtils,
186                         string_compose("No file matching %1 found in Path: %2\n", filename, search_path.to_string())
187                             );
188                 return false;
189         }
190
191         if (tmp.size() != 1)
192         {
193                 DEBUG_TRACE (
194                         DEBUG::FileUtils,
195                         string_compose("Found more that one file matching %1 in Path: %2\n", filename, search_path.to_string())
196                             );
197         }
198
199         result = tmp.front();
200
201         DEBUG_TRACE (
202                 DEBUG::FileUtils,
203                 string_compose("Found file %1 in Path: %2\n", filename, search_path.to_string())
204                     );
205
206         return true;
207 }
208
209 static
210 bool
211 regexp_filter (const string& str, void *arg)
212 {
213         regex_t* pattern = (regex_t*)arg;
214         return regexec (pattern, str.c_str(), 0, 0, 0) == 0;
215 }
216
217 void
218 find_files_matching_regex (vector<string>& result,
219                            const std::string& dirpath,
220                            const std::string& regexp,
221                            bool match_fullpath, bool return_fullpath,
222                            long limit,
223                            bool recurse)
224 {
225         int err;
226         char msg[256];
227         regex_t compiled_pattern;
228
229         if ((err = regcomp (&compiled_pattern, regexp.c_str(),
230                             REG_EXTENDED|REG_NOSUB))) {
231
232                 regerror (err, &compiled_pattern,
233                           msg, sizeof (msg));
234
235                 error << "Cannot compile soundfile regexp for use ("
236                       << msg
237                       << ")"
238                       << endmsg;
239
240                 return;
241         }
242
243         find_files_matching_filter (result, dirpath,
244                                     regexp_filter, &compiled_pattern,
245                                     match_fullpath, return_fullpath,
246                                     limit, recurse);
247
248         regfree (&compiled_pattern);
249 }
250
251 void
252 find_files_matching_filter (vector<string>& result,
253                             const string &dirpath,
254                             bool (*filter)(const string &, void *),
255                             void *arg,
256                             bool match_fullpath, bool return_fullpath,
257                             long limit,
258                             bool recurse)
259 {
260         DIR *dir;
261         struct dirent *finfo;
262         char *pathcopy = strdup (search_path_expand (dirpath).c_str());
263         char *thisdir;
264         string fullpath;
265         string search_str;
266         long nfound = 0;
267         char *saveptr;
268
269         if ((thisdir = strtok_r (pathcopy, G_SEARCHPATH_SEPARATOR_S, &saveptr)) == 0 ||
270             strlen (thisdir) == 0) {
271                 free (pathcopy);
272                 return;
273         }
274
275         do {
276
277                 if ((dir = opendir (thisdir)) == 0) {
278                         continue;
279                 }
280
281                 while ((finfo = readdir (dir)) != 0) {
282
283                         if ((finfo->d_name[0] == '.' && finfo->d_name[1] == '\0') ||
284                             (finfo->d_name[0] == '.' && finfo->d_name[1] == '.' && finfo->d_name[2] == '\0')) {
285                                 continue;
286                         }
287
288                         fullpath = Glib::build_filename (thisdir, finfo->d_name);
289
290                         struct stat statbuf;
291                         if (stat (fullpath.c_str(), &statbuf) < 0) {
292                                 continue;
293                         }
294
295                         if (statbuf.st_mode & S_IFDIR && recurse) {
296                                 find_files_matching_filter (result, fullpath, filter, arg, match_fullpath, return_fullpath, limit, recurse);
297                         } else {
298
299                                 if (match_fullpath) {
300                                         search_str = fullpath;
301                                 } else {
302                                         search_str = finfo->d_name;
303                                 }
304
305                                 if (!filter(search_str, arg)) {
306                                         continue;
307                                 }
308
309                                 if (return_fullpath) {
310                                         result.push_back(fullpath);
311                                 } else {
312                                         result.push_back(finfo->d_name);
313                                 }
314
315                                 nfound++;
316                         }
317                 }
318                 closedir (dir);
319
320         } while ((limit < 0 || (nfound < limit)) && (thisdir = strtok_r (0, G_SEARCHPATH_SEPARATOR_S, &saveptr)));
321
322         free (pathcopy);
323         return;
324 }
325
326 bool
327 copy_file(const std::string & from_path, const std::string & to_path)
328 {
329         if (!Glib::file_test (from_path, Glib::FILE_TEST_EXISTS)) return false;
330
331         int fd_from = -1;
332         int fd_to = -1;
333         char buf[4096]; // BUFSIZ  ??
334         ssize_t nread;
335
336         fd_from = ::open(from_path.c_str(), O_RDONLY);
337         if (fd_from < 0) {
338                 goto copy_error;
339         }
340
341         fd_to = ::open(to_path.c_str(), O_WRONLY | O_CREAT, 0666);
342         if (fd_to < 0) {
343                 goto copy_error;
344         }
345
346         while (nread = ::read(fd_from, buf, sizeof(buf)), nread > 0) {
347                 char *out_ptr = buf;
348                 do {
349                         ssize_t nwritten = ::write(fd_to, out_ptr, nread);
350                         if (nwritten >= 0) {
351                                 nread -= nwritten;
352                                 out_ptr += nwritten;
353                         } else if (errno != EINTR) {
354                                 goto copy_error;
355                         }
356                 } while (nread > 0);
357         }
358
359         if (nread == 0) {
360                 if (::close(fd_to)) {
361                         fd_to = -1;
362                         goto copy_error;
363                 }
364                 ::close(fd_from);
365                 return true;
366         }
367
368 copy_error:
369         int saved_errno = errno;
370
371         if (fd_from >= 0) {
372                 ::close(fd_from);
373         }
374         if (fd_to >= 0) {
375                 ::close(fd_to);
376         }
377
378         error << string_compose (_("Unable to Copy file %1 to %2 (%3)"),
379                         from_path, to_path, strerror(saved_errno))
380                 << endmsg;
381         return false;
382 }
383
384 static
385 bool accept_all_files (string const &, void *)
386 {
387         return true;
388 }
389
390 void
391 copy_files(const std::string & from_path, const std::string & to_dir)
392 {
393         vector<string> files;
394         find_files_matching_filter (files, from_path, accept_all_files, 0, true, false);
395
396         for (vector<string>::iterator i = files.begin(); i != files.end(); ++i) {
397                 std::string from = Glib::build_filename (from_path, *i);
398                 std::string to = Glib::build_filename (to_dir, *i);
399                 copy_file (from, to);
400         }
401 }
402
403 std::string
404 get_absolute_path (const std::string & p)
405 {
406         if (Glib::path_is_absolute(p)) return p;
407         return Glib::build_filename (Glib::get_current_dir(), p);
408 }
409
410 bool
411 equivalent_paths (const std::string& a, const std::string& b)
412 {
413         GStatBuf bA;
414         int const rA = g_stat (a.c_str(), &bA);
415         GStatBuf bB;
416         int const rB = g_stat (b.c_str(), &bB);
417
418         return (rA == 0 && rB == 0 && bA.st_dev == bB.st_dev && bA.st_ino == bB.st_ino);
419 }
420
421 bool
422 path_is_within (std::string const & haystack, std::string needle)
423 {
424         while (1) {
425                 if (equivalent_paths (haystack, needle)) {
426                         return true;
427                 }
428
429                 needle = Glib::path_get_dirname (needle);
430                 if (needle == "." || needle == "/") {
431                         break;
432                 }
433         }
434
435         return false;
436 }
437
438 bool
439 exists_and_writable (const std::string & p)
440 {
441         /* writable() really reflects the whole folder, but if for any
442            reason the session state file can't be written to, still
443            make us unwritable.
444         */
445
446         GStatBuf statbuf;
447
448         if (g_stat (p.c_str(), &statbuf) != 0) {
449                 /* doesn't exist - not writable */
450                 return false;
451         } else {
452                 if (!(statbuf.st_mode & S_IWUSR)) {
453                         /* exists and is not writable */
454                         return false;
455                 }
456                 /* filesystem may be mounted read-only, so even though file
457                  * permissions permit access, the mount status does not.
458                  * access(2) seems like the best test for this.
459                  */
460                 if (g_access (p.c_str(), W_OK) != 0) {
461                         return false;
462                 }
463         }
464
465         return true;
466 }
467
468 } // namespace PBD