2 * @brief File and path manipulation routines.
4 /* Copyright (C) 2008 Lemur Consulting Ltd
5 * Copyright (C) 2008,2009,2010,2012 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 "safeerrno.h"
29 #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
);
83 /// Return true iff a path starts with a drive letter.
85 has_drive(const string
&path
)
87 return (path
.size() >= 2 && path
[1] == ':');
90 /// Return true iff path is a UNCW path.
92 uncw_path(const string
& path
)
94 return (path
.size() >= 4 && memcmp(path
.data(), "\\\\?\\", 4) == 0);
97 inline bool slash(char ch
)
99 return ch
== '/' || ch
== '\\';
104 resolve_relative_path(string
& path
, const string
& base
)
107 if (path
.empty() || path
[0] != '/') {
109 string::size_type last_slash
= base
.rfind('/');
110 if (last_slash
!= string::npos
)
111 path
.insert(0, base
, 0, last_slash
+ 1);
114 // Microsoft Windows paths may begin with a drive letter but still be
115 // relative within that drive.
116 bool drive
= has_drive(path
);
117 string::size_type p
= (drive
? 2 : 0);
118 bool absolute
= (p
!= path
.size() && slash(path
[p
]));
121 // If path is absolute and has a drive specifier, just return it.
125 // If base has a drive specifier prepend that to path.
126 if (has_drive(base
)) {
127 path
.insert(0, base
, 0, 2);
131 // If base has a UNC (\\SERVER\\VOLUME) or \\?\ prefix, prepend that
133 if (uncw_path(base
)) {
134 string::size_type sl
= 0;
135 if (base
.size() >= 7 && memcmp(base
.data() + 5, ":\\", 2) == 0) {
138 } else if (base
.size() >= 8 &&
139 memcmp(base
.data() + 4, "UNC\\", 4) == 0) {
140 // "\\?\UNC\server\volume\"
141 sl
= base
.find('\\', 8);
142 if (sl
!= string::npos
)
143 sl
= base
.find('\\', sl
+ 1);
146 // With the \\?\ prefix, '/' isn't recognised so change it
149 for (i
= path
.begin(); i
!= path
.end(); ++i
) {
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
179 for (i
= path
.begin(); i
!= path
.end(); ++i
) {
184 path
.insert(b
, base
, b
, last_slash
+ 1 - b
);