Support: quest -f cjk_ngram
[xapian.git] / xapian-core / common / fileutils.cc
blobcc71eaa6938d1b31c6239aa1b1ad982a421620a5
1 /** @file fileutils.cc
2 * @brief File and path manipulation routines.
3 */
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
22 #include <config.h>
24 #include "fileutils.h"
26 #include "xapian/error.h"
27 #include "safedirent.h"
28 #include "safeerrno.h"
29 #include "safeunistd.h"
31 #include <cstring>
32 #include <string>
33 #include <sys/types.h>
35 using namespace std;
37 class dircloser {
38 DIR * dir;
39 public:
40 dircloser(DIR * dir_) : dir(dir_) {}
41 ~dircloser() {
42 if (dir != NULL) {
43 closedir(dir);
44 dir = NULL;
49 void
50 removedir(const string &dirname)
52 DIR * dir;
54 dir = opendir(dirname.c_str());
55 if (dir == NULL) {
56 if (errno == ENOENT) return;
57 throw Xapian::DatabaseError("Cannot open directory '" + dirname + "'", errno);
61 dircloser dc(dir);
62 while (true) {
63 errno = 0;
64 struct dirent * entry = readdir(dir);
65 if (entry == NULL) {
66 if (errno == 0)
67 break;
68 throw Xapian::DatabaseError("Cannot read entry from directory at '" + dirname + "'", errno);
70 string name(entry->d_name);
71 if (name == "." || name == "..")
72 continue;
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);
82 #ifdef __WIN32__
83 /// Return true iff a path starts with a drive letter.
84 static bool
85 has_drive(const string &path)
87 return (path.size() >= 2 && path[1] == ':');
90 /// Return true iff path is a UNCW path.
91 static bool
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 == '\\';
101 #endif
103 void
104 resolve_relative_path(string & path, const string & base)
106 #ifndef __WIN32__
107 if (path.empty() || path[0] != '/') {
108 // path is relative.
109 string::size_type last_slash = base.rfind('/');
110 if (last_slash != string::npos)
111 path.insert(0, base, 0, last_slash + 1);
113 #else
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]));
120 if (absolute) {
121 // If path is absolute and has a drive specifier, just return it.
122 if (drive)
123 return;
125 // If base has a drive specifier prepend that to path.
126 if (has_drive(base)) {
127 path.insert(0, base, 0, 2);
128 return;
131 // If base has a UNC (\\SERVER\\VOLUME) or \\?\ prefix, prepend that
132 // to path.
133 if (uncw_path(base)) {
134 string::size_type sl = 0;
135 if (base.size() >= 7 && memcmp(base.data() + 5, ":\\", 2) == 0) {
136 // "\\?\X:\"
137 sl = 6;
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);
145 if (sl) {
146 // With the \\?\ prefix, '/' isn't recognised so change it
147 // to '\' in path.
148 string::iterator i;
149 for (i = path.begin(); i != path.end(); ++i) {
150 if (*i == '/')
151 *i = '\\';
153 path.insert(0, base, 0, sl);
155 } else if (base.size() >= 5 && slash(base[0]) && slash(base[1])) {
156 // Handle UNC base.
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);
163 return;
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)
172 last_slash = 1;
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
177 // to '\' in path.
178 string::iterator i;
179 for (i = path.begin(); i != path.end(); ++i) {
180 if (*i == '/')
181 *i = '\\';
184 path.insert(b, base, b, last_slash + 1 - b);
187 #endif