1 /*
2  * Copyright (C) 1998-2016 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2007-2016 Tim Mayberry <mojofunk@gmail.com>
4  * Copyright (C) 2008-2009 David Robillard <d@drobilla.net>
5  * Copyright (C) 2013-2015 John Emmas <john@creativepost.co.uk>
6  * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22 
23 #include <algorithm>
24 #include <vector>
25 
26 #include <glib.h>
27 #include "pbd/gstdio_compat.h"
28 
29 #ifdef COMPILER_MINGW
30 #include <io.h> // For W_OK
31 #include <windows.h>
32 #endif
33 
34 #include <glibmm/fileutils.h>
35 #include <glibmm/miscutils.h>
36 #include <glibmm/pattern.h>
37 
38 #include <errno.h>
39 #include <string.h> /* strerror */
40 
41 /* open() */
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <fcntl.h>
45 
46 /* close(), read(), write() */
47 #ifdef COMPILER_MSVC
48 #include <io.h> // Microsoft's nearest equivalent to <unistd.h>
49 #include <ardourext/misc.h>
50 #else
51 #include <unistd.h>
52 #include <regex.h>
53 #endif
54 
55 #include "pbd/compose.h"
56 #include "pbd/file_utils.h"
57 #include "pbd/debug.h"
58 #include "pbd/error.h"
59 #include "pbd/pathexpand.h"
60 #include "pbd/scoped_file_descriptor.h"
61 #include "pbd/stl_delete.h"
62 
63 #include "pbd/i18n.h"
64 
65 using namespace std;
66 
67 namespace PBD {
68 
69 static void
run_functor_for_paths(vector<string> & result,const Searchpath & paths,bool (* functor)(const string &,void *),void * arg,bool pass_files_only,bool pass_fullpath,bool return_fullpath,bool recurse)70 run_functor_for_paths (vector<string>& result,
71                        const Searchpath& paths,
72                        bool (*functor)(const string &, void *),
73                        void *arg,
74                        bool pass_files_only,
75                        bool pass_fullpath, bool return_fullpath,
76                        bool recurse)
77 {
78 	for (vector<string>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
79 		string expanded_path = path_expand (*i);
80 		DEBUG_TRACE (DEBUG::FileUtils,
81 				string_compose("Find files in expanded path: %1\n", expanded_path));
82 
83 		if (!Glib::file_test (expanded_path, Glib::FILE_TEST_IS_DIR)) continue;
84 
85 		try
86 		{
87 			Glib::Dir dir(expanded_path);
88 
89 			for (Glib::DirIterator di = dir.begin(); di != dir.end(); di++) {
90 
91 				string fullpath = Glib::build_filename (expanded_path, *di);
92 				string basename = *di;
93 
94 				bool is_dir = Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR);
95 
96 				if (is_dir && recurse) {
97 					DEBUG_TRACE (DEBUG::FileUtils,
98 							string_compose("Descending into directory:  %1\n",
99 								fullpath));
100 					run_functor_for_paths (result, fullpath, functor, arg, pass_files_only,
101 					                       pass_fullpath, return_fullpath, recurse);
102 				}
103 
104 				if (is_dir && pass_files_only) {
105 					continue;
106 				}
107 
108 				string functor_str;
109 
110 				if (pass_fullpath) {
111 					functor_str = fullpath;
112 				} else {
113 					functor_str = basename;
114 				}
115 
116 				DEBUG_TRACE (DEBUG::FileUtils,
117 						string_compose("Run Functor using string: %1\n", functor_str));
118 
119 				if (!functor(functor_str, arg)) {
120 					continue;
121 				}
122 
123 				DEBUG_TRACE (DEBUG::FileUtils,
124 						string_compose("Found file %1 matching functor\n", functor_str));
125 
126 				if (return_fullpath) {
127 					result.push_back(fullpath);
128 				} else {
129 					result.push_back(basename);
130 				}
131 			}
132 		}
133 		catch (Glib::FileError const& err)
134 		{
135 			warning << err.what() << endmsg;
136 		}
137 	}
138 }
139 
140 static
accept_all_files(string const &,void *)141 bool accept_all_files (string const &, void *)
142 {
143 	return true;
144 }
145 
146 void
get_paths(vector<string> & result,const Searchpath & paths,bool files_only,bool recurse)147 get_paths (vector<string>& result,
148            const Searchpath& paths,
149            bool files_only,
150            bool recurse)
151 {
152 	run_functor_for_paths (result, paths, accept_all_files, 0,
153 	                       files_only, true, true, recurse);
154 }
155 
156 void
get_files(vector<string> & result,const Searchpath & paths)157 get_files (vector<string>& result, const Searchpath& paths)
158 {
159 	return get_paths (result, paths, true, false);
160 }
161 
162 static
163 bool
pattern_filter(const string & str,void * arg)164 pattern_filter (const string& str, void *arg)
165 {
166 	Glib::PatternSpec* pattern = (Glib::PatternSpec*)arg;
167 	return pattern->match(str);
168 }
169 
170 void
find_files_matching_pattern(vector<string> & result,const Searchpath & paths,const Glib::PatternSpec & pattern)171 find_files_matching_pattern (vector<string>& result,
172                              const Searchpath& paths,
173                              const Glib::PatternSpec& pattern)
174 {
175 	run_functor_for_paths (result, paths, pattern_filter,
176 	                       const_cast<Glib::PatternSpec*>(&pattern),
177 	                       true, false, true, false);
178 }
179 
180 void
find_files_matching_pattern(vector<string> & result,const Searchpath & paths,const string & pattern)181 find_files_matching_pattern (vector<string>& result,
182                              const Searchpath& paths,
183                              const string& pattern)
184 {
185 	Glib::PatternSpec tmp(pattern);
186 	find_files_matching_pattern (result, paths, tmp);
187 }
188 
189 bool
find_file(const Searchpath & search_path,const string & filename,std::string & result)190 find_file (const Searchpath& search_path,
191            const string& filename,
192            std::string& result)
193 {
194 	vector<std::string> tmp;
195 
196 	find_files_matching_pattern (tmp, search_path, filename);
197 
198 	if (tmp.size() == 0) {
199 		DEBUG_TRACE (DEBUG::FileUtils,
200 		             string_compose("No file matching %1 found in Path: %2\n",
201 		             filename, search_path.to_string()));
202 		return false;
203 	}
204 
205 	if (tmp.size() != 1) {
206 		DEBUG_TRACE (DEBUG::FileUtils,
207 		             string_compose("Found more that one file matching %1 in Path: %2\n",
208 		             filename, search_path.to_string()));
209 	}
210 
211 	result = tmp.front();
212 
213 	DEBUG_TRACE (DEBUG::FileUtils,
214 	             string_compose("Found file %1 in Path: %2\n",
215 	             filename, search_path.to_string()));
216 
217 	return true;
218 }
219 
220 static
221 bool
regexp_filter(const string & str,void * arg)222 regexp_filter (const string& str, void *arg)
223 {
224 	regex_t* pattern = (regex_t*)arg;
225 	return regexec (pattern, str.c_str(), 0, 0, 0) == 0;
226 }
227 
228 void
find_files_matching_regex(vector<string> & result,const Searchpath & paths,const std::string & regexp,bool recurse)229 find_files_matching_regex (vector<string>& result,
230                            const Searchpath& paths,
231                            const std::string& regexp,
232                            bool recurse)
233 {
234 	int err;
235 	char msg[256];
236 	regex_t compiled_pattern;
237 
238 	if ((err = regcomp (&compiled_pattern, regexp.c_str(),
239 			    REG_EXTENDED|REG_NOSUB))) {
240 
241 		regerror (err, &compiled_pattern,
242 			  msg, sizeof (msg));
243 
244 		error << "Cannot compile soundfile regexp for use ("
245 		      << msg
246 		      << ")"
247 		      << endmsg;
248 
249 		return;
250 	}
251 
252 	DEBUG_TRACE (DEBUG::FileUtils,
253 			string_compose("Matching files using regexp: %1\n", regexp));
254 
255 	find_files_matching_filter (result, paths,
256 	                            regexp_filter, &compiled_pattern,
257 	                            true, true, recurse);
258 
259 	regfree (&compiled_pattern);
260 }
261 
262 void
find_paths_matching_filter(vector<string> & result,const Searchpath & paths,bool (* filter)(const string &,void *),void * arg,bool pass_fullpath,bool return_fullpath,bool recurse)263 find_paths_matching_filter (vector<string>& result,
264                             const Searchpath& paths,
265                             bool (*filter)(const string &, void *),
266                             void *arg,
267                             bool pass_fullpath, bool return_fullpath,
268                             bool recurse)
269 {
270 	run_functor_for_paths (result, paths, filter, arg, false, pass_fullpath, return_fullpath, recurse);
271 }
272 
273 void
find_files_matching_filter(vector<string> & result,const Searchpath & paths,bool (* filter)(const string &,void *),void * arg,bool pass_fullpath,bool return_fullpath,bool recurse)274 find_files_matching_filter (vector<string>& result,
275                             const Searchpath& paths,
276                             bool (*filter)(const string &, void *),
277                             void *arg,
278                             bool pass_fullpath, bool return_fullpath,
279                             bool recurse)
280 {
281 	run_functor_for_paths (result, paths, filter, arg, true, pass_fullpath, return_fullpath, recurse);
282 }
283 
284 bool
copy_file(const std::string & from_path,const std::string & to_path)285 copy_file(const std::string & from_path, const std::string & to_path)
286 {
287 	if (!Glib::file_test (from_path, Glib::FILE_TEST_EXISTS)) return false;
288 
289 	PBD::ScopedFileDescriptor fd_from (g_open (from_path.c_str(), O_RDONLY, 0444));
290 	PBD::ScopedFileDescriptor fd_to (g_open (to_path.c_str(), O_CREAT|O_TRUNC|O_RDWR, 0666));
291 
292 	char buf[4096]; // BUFSIZ  ??
293 	ssize_t nread;
294 
295 	if ((fd_from < 0) || (fd_to < 0)) {
296 		error << string_compose (_("Unable to Open files %1 to %2 for Copying(%3)"),
297 				from_path, to_path, g_strerror(errno))
298 		      << endmsg;
299 		return false;
300 	}
301 
302 	while (nread = ::read(fd_from, buf, sizeof(buf)), nread > 0) {
303 		char *out_ptr = buf;
304 		do {
305 			ssize_t nwritten = ::write(fd_to, out_ptr, nread);
306 			if (nwritten >= 0) {
307 				nread -= nwritten;
308 				out_ptr += nwritten;
309 			} else if (errno != EINTR) {
310 				error << string_compose (_("Unable to Copy files %1 to %2(%3)"),
311 						from_path, to_path, g_strerror(errno))
312 					<< endmsg;
313 				return false;
314 			}
315 		} while (nread > 0);
316 	}
317 
318 	return true;
319 }
320 
321 void
copy_files(const std::string & from_path,const std::string & to_dir)322 copy_files(const std::string & from_path, const std::string & to_dir)
323 {
324 	vector<string> files;
325 	find_files_matching_filter (files, from_path, accept_all_files, 0, true, false);
326 
327 	for (vector<string>::iterator i = files.begin(); i != files.end(); ++i) {
328 		std::string from = Glib::build_filename (from_path, *i);
329 		std::string to = Glib::build_filename (to_dir, *i);
330 		copy_file (from, to);
331 	}
332 }
333 
334 void
copy_recurse(const std::string & from_path,const std::string & to_dir)335 copy_recurse(const std::string & from_path, const std::string & to_dir)
336 {
337 	vector<string> files;
338 	find_files_matching_filter (files, from_path, accept_all_files, 0, false, true, true);
339 
340 	const size_t prefix_len = from_path.size();
341 	for (vector<string>::iterator i = files.begin(); i != files.end(); ++i) {
342 		std::string from = *i;
343 		std::string to = Glib::build_filename (to_dir, (*i).substr(prefix_len));
344 		g_mkdir_with_parents (Glib::path_get_dirname (to).c_str(), 0755);
345 		copy_file (from, to);
346 	}
347 }
348 
349 bool
touch_file(const std::string & path)350 touch_file (const std::string& path)
351 {
352 	int fd = g_open (path.c_str(), O_RDWR|O_CREAT, 0660);
353 	if (fd >= 0) {
354 		close (fd);
355 		return true;
356 	}
357 	return false;
358 }
359 
360 bool
hard_link(const std::string & existing_file,const std::string & new_path)361 hard_link (const std::string& existing_file, const std::string& new_path)
362 {
363 #ifdef PLATFORM_WINDOWS
364 # if defined (COMPILER_MINGW) && defined(__GNUC__) && __GNUC__ == 8
365 	/* For some reason mingx 8.3.0 does not support CreateHardLinkA()
366 	 * (mingw/gcc-4.9 does) */
367 	return false;
368 # else
369 	/* see also ntfs_link -- msvc only pbd extension */
370 	return CreateHardLinkA (new_path.c_str(), existing_file.c_str(), NULL);
371 # endif
372 #else
373 	return 0 == link (existing_file.c_str(), new_path.c_str());
374 #endif
375 }
376 
377 std::string
get_absolute_path(const std::string & p)378 get_absolute_path (const std::string & p)
379 {
380 	if (Glib::path_is_absolute(p)) return p;
381 	return Glib::build_filename (Glib::get_current_dir(), p);
382 }
383 
384 string
canonical_path(const std::string & path)385 canonical_path (const std::string& path)
386 {
387 #ifdef PLATFORM_WINDOWS
388 	wchar_t resolved_wpath[_MAX_PATH];
389 
390 	// sizeof(wchar_t) is 2 bytes using gcc/mingw and VC++ but 4 bytes using gcc/linux
391 	assert (sizeof(wchar_t) == 2);
392 
393 	wchar_t* wfilepath = (wchar_t*)g_utf8_to_utf16 (path.c_str(), -1, NULL, NULL, NULL);
394 
395 	if (wfilepath == NULL) {
396 		DEBUG_TRACE (
397 		    DEBUG::FileUtils,
398 		    string_compose ("PBD::canonical_path: Unable to convert path from utf8 to utf16 : %1\n",
399 		                    path));
400 		return path;
401 	}
402 
403 	if (_wfullpath (resolved_wpath, wfilepath, _MAX_PATH) == NULL) {
404 		DEBUG_TRACE (DEBUG::FileUtils,
405 		             string_compose ("PBD::canonical_path: Unable to resolve %1\n", wfilepath));
406 		return path;
407 	}
408 
409 	gchar* resolved_utf8_path =
410 	    g_utf16_to_utf8 (reinterpret_cast<const gunichar2*>(resolved_wpath), -1, NULL, NULL, NULL);
411 
412 	if (resolved_utf8_path == NULL) {
413 		DEBUG_TRACE (
414 		    DEBUG::FileUtils,
415 		    string_compose ("PBD::canonical_path: Unable to convert path from utf16 to utf8 : %1\n",
416 		                    resolved_wpath));
417 		return path;
418 	}
419 
420 	const string retval(resolved_utf8_path);
421 
422 	g_free (wfilepath);
423 	g_free (resolved_utf8_path);
424 
425 	return retval;
426 
427 #else
428 	char buf[PATH_MAX+1];
429 
430 	if (realpath (path.c_str(), buf) == NULL) {
431 		DEBUG_TRACE (DEBUG::FileUtils,
432 		             string_compose ("PBD::canonical_path: Unable to resolve %1: %2\n", path,
433 		                             g_strerror (errno)));
434 		return path;
435 	}
436 	DEBUG_TRACE (DEBUG::FileUtils,
437 	             string_compose ("PBD::canonical_path %1 resolved to: %2\n", path, string (buf)));
438 
439 	return string (buf);
440 #endif
441 }
442 
443 std::string
get_suffix(const std::string & p)444 get_suffix (const std::string & p)
445 {
446 	string::size_type period = p.find_last_of ('.');
447 	if (period == string::npos || period == p.length() - 1) {
448 		return string();
449 	}
450 	return p.substr (period+1);
451 }
452 
453 bool
equivalent_paths(const std::string & a,const std::string & b)454 equivalent_paths (const std::string& a, const std::string& b)
455 {
456 	GStatBuf bA;
457 	int const rA = g_stat (a.c_str(), &bA);
458 	GStatBuf bB;
459 	int const rB = g_stat (b.c_str(), &bB);
460 
461 	return (rA == 0 && rB == 0 && bA.st_dev == bB.st_dev && bA.st_ino == bB.st_ino);
462 }
463 
464 bool
path_is_within(std::string const & haystack,std::string needle)465 path_is_within (std::string const & haystack, std::string needle)
466 {
467 	while (1) {
468 		if (equivalent_paths (haystack, needle)) {
469 			return true;
470 		}
471 
472 		needle = Glib::path_get_dirname (needle);
473 		if (needle == "." || needle == "/" || Glib::path_skip_root(needle).empty()) {
474 			break;
475 		}
476 	}
477 
478 	return false;
479 }
480 
481 bool
exists_and_writable(const std::string & p)482 exists_and_writable (const std::string & p)
483 {
484 	/* writable() really reflects the whole folder, but if for any
485 	   reason the session state file can't be written to, still
486 	   make us unwritable.
487 	*/
488 
489 	GStatBuf statbuf;
490 
491 	if (g_stat (p.c_str(), &statbuf) != 0) {
492 		/* doesn't exist - not writable */
493 		return false;
494 	} else {
495 		if (!(statbuf.st_mode & S_IWUSR)) {
496 			/* exists and is not writable */
497 			return false;
498 		}
499 		/* filesystem may be mounted read-only, so even though file
500 		 * permissions permit access, the mount status does not.
501 		 * access(2) seems like the best test for this.
502 		 */
503 		if (g_access (p.c_str(), W_OK) != 0) {
504 			return false;
505 		}
506 	}
507 
508 	return true;
509 }
510 
511 int
remove_directory_internal(const string & dir,size_t * size,vector<string> * paths,bool just_remove_files)512 remove_directory_internal (const string& dir, size_t* size, vector<string>* paths,
513                            bool just_remove_files)
514 {
515 	vector<string> tmp_paths;
516 	GStatBuf statbuf;
517 	int ret = 0;
518 
519 	get_paths (tmp_paths, dir, just_remove_files, true);
520 
521 	for (vector<string>::const_iterator i = tmp_paths.begin();
522 			i != tmp_paths.end(); ++i) {
523 
524 		if (g_stat (i->c_str(), &statbuf)) {
525 			continue;
526 		}
527 
528 		if (::g_remove (i->c_str())) {
529 			error << string_compose (_("cannot remove path %1 (%2)"), *i, strerror (errno))
530 				<< endmsg;
531 			ret = 1;
532 			continue;
533 		}
534 
535 		if (paths) {
536 			paths->push_back (Glib::path_get_basename(*i));
537 		}
538 
539 		// statbuf.st_size is off_t
540 		if (size && statbuf.st_size > 0) {
541 			*size += statbuf.st_size;
542 		}
543 
544 	}
545 
546 	return ret;
547 }
548 
549 int
clear_directory(const string & dir,size_t * size,vector<string> * paths)550 clear_directory (const string& dir, size_t* size, vector<string>* paths)
551 {
552 	return remove_directory_internal (dir, size, paths, true);
553 }
554 
555 // rm -rf <dir> -- used to remove saved plugin state
556 void
remove_directory(const std::string & dir)557 remove_directory (const std::string& dir)
558 {
559 	remove_directory_internal (dir, 0, 0, false);
560 	g_rmdir (dir.c_str());
561 }
562 
563 string
tmp_writable_directory(const char * domain,const string & prefix)564 tmp_writable_directory (const char* domain, const string& prefix)
565 {
566 	std::string tmp_dir = Glib::build_filename (g_get_tmp_dir(), domain);
567 	std::string dir_name;
568 	std::string new_test_dir;
569 	do {
570 		ostringstream oss;
571 		oss << prefix;
572 		oss << g_random_int ();
573 		dir_name = oss.str();
574 		new_test_dir = Glib::build_filename (tmp_dir, dir_name);
575 		if (Glib::file_test (new_test_dir, Glib::FILE_TEST_EXISTS)) continue;
576 	} while (g_mkdir_with_parents (new_test_dir.c_str(), 0755) != 0);
577 	return new_test_dir;
578 }
579 
580 int
toggle_file_existence(string const & path)581 toggle_file_existence (string const & path)
582 {
583 	if (Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR)) {
584 		return g_unlink (path.c_str());
585 	}
586 
587 	PBD::ScopedFileDescriptor fd = g_open (path.c_str(), O_CREAT|O_TRUNC|O_RDWR, 0666);
588 	return !((int) fd >= 0);
589 }
590 
591 } // namespace PBD
592