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