2 * @brief File and path manipulation routines.
4 /* Copyright (C) 2008 Lemur Consulting Ltd
5 * Copyright (C) 2008,2009,2010,2012,2024 Olly Betts
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 #include "fileutils.h"
26 #include "xapian/error.h"
27 #include "safedirent.h"
28 #include "safeunistd.h"
33 #include <sys/types.h>
40 dircloser(DIR * dir_
) : dir(dir_
) {}
50 removedir(const string
&dirname
)
54 dir
= opendir(dirname
.c_str());
56 if (errno
== ENOENT
) return;
57 throw Xapian::DatabaseError("Cannot open directory '" + dirname
+ "'", errno
);
64 struct dirent
* entry
= readdir(dir
);
68 throw Xapian::DatabaseError("Cannot read entry from directory at '" + dirname
+ "'", errno
);
70 string
name(entry
->d_name
);
71 if (name
== "." || name
== "..")
73 if (unlink((dirname
+ "/" + name
).c_str())) {
74 throw Xapian::DatabaseError("Cannot remove file '" + string(entry
->d_name
) + "'", errno
);
78 if (rmdir(dirname
.c_str())) {
79 throw Xapian::DatabaseError("Cannot remove directory '" + dirname
+ "'", errno
);
84 /// Return true iff a path starts with a drive letter.
86 has_drive(string_view path
)
88 return (path
.size() >= 2 && path
[1] == ':');
91 /// Return true iff path is a UNCW path.
93 uncw_path(string_view path
)
95 return (path
.size() >= 4 && memcmp(path
.data(), "\\\\?\\", 4) == 0);
98 static inline bool slash(char ch
)
100 return ch
== '/' || ch
== '\\';
105 resolve_relative_path(string
& path
, string_view base
)
108 if (path
.empty() || path
[0] != '/') {
110 string::size_type last_slash
= base
.rfind('/');
111 if (last_slash
!= string::npos
)
112 path
.insert(0, base
, 0, last_slash
+ 1);
115 // Microsoft Windows paths may begin with a drive letter but still be
116 // relative within that drive.
117 bool drive
= has_drive(path
);
118 string::size_type p
= (drive
? 2 : 0);
119 bool absolute
= (p
!= path
.size() && slash(path
[p
]));
122 // If path is absolute and has a drive specifier, just return it.
126 // If base has a drive specifier prepend that to path.
127 if (has_drive(base
)) {
128 path
.insert(0, base
, 0, 2);
132 // If base has a UNC (\\SERVER\\VOLUME) or \\?\ prefix, prepend that
134 if (uncw_path(base
)) {
135 string::size_type sl
= 0;
136 if (base
.size() >= 7 && memcmp(base
.data() + 5, ":\\", 2) == 0) {
139 } else if (base
.size() >= 8 &&
140 memcmp(base
.data() + 4, "UNC\\", 4) == 0) {
141 // "\\?\UNC\server\volume\"
142 sl
= base
.find('\\', 8);
143 if (sl
!= string::npos
)
144 sl
= base
.find('\\', sl
+ 1);
147 // With the \\?\ prefix, '/' isn't recognised so change it
149 for (auto& ch
: path
) {
153 path
.insert(0, base
, 0, sl
);
155 } else if (base
.size() >= 5 && slash(base
[0]) && slash(base
[1])) {
157 string::size_type sl
= base
.find_first_of("/\\", 2);
158 if (sl
!= string::npos
) {
159 sl
= base
.find_first_of("/\\", sl
+ 1);
160 path
.insert(0, base
, 0, sl
);
166 // path is relative, so if it has no drive specifier or the same drive
167 // specifier as base, then we want to qualify it using base.
168 bool base_drive
= has_drive(base
);
169 if (!drive
|| (base_drive
&& (path
[0] | 32) == (base
[0] | 32))) {
170 string::size_type last_slash
= base
.find_last_of("/\\");
171 if (last_slash
== string::npos
&& !drive
&& base_drive
)
173 if (last_slash
!= string::npos
) {
174 string::size_type b
= (drive
&& base_drive
? 2 : 0);
175 if (uncw_path(base
)) {
176 // With the \\?\ prefix, '/' isn't recognised so change it
178 for (auto& ch
: path
) {
183 path
.insert(b
, base
, b
, last_slash
+ 1 - b
);