Branch data Line data Source code
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
6 : : *
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.
11 : : *
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.
16 : : *
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
20 : : */
21 : :
22 : : #include <config.h>
23 : :
24 : : #include "fileutils.h"
25 : :
26 : : #include "xapian/error.h"
27 : : #include "safedirent.h"
28 : : #include "safeunistd.h"
29 : :
30 : : #include <cerrno>
31 : : #include <cstring>
32 : : #include <string>
33 : : #include <sys/types.h>
34 : :
35 : : using namespace std;
36 : :
37 : : class dircloser {
38 : : DIR * dir;
39 : : public:
40 : 9 : dircloser(DIR * dir_) : dir(dir_) {}
41 : 9 : ~dircloser() {
42 [ + - ]: 9 : if (dir != NULL) {
43 : 9 : closedir(dir);
44 : 9 : dir = NULL;
45 : : }
46 : 9 : }
47 : : };
48 : :
49 : : void
50 : 18 : removedir(const string &dirname)
51 : : {
52 : : DIR * dir;
53 : :
54 : 18 : dir = opendir(dirname.c_str());
55 [ + + ]: 18 : if (dir == NULL) {
56 [ + - ]: 18 : if (errno == ENOENT) return;
57 [ # # ][ # # ]: 0 : throw Xapian::DatabaseError("Cannot open directory '" + dirname + "'", errno);
[ # # ]
58 : : }
59 : :
60 : : {
61 : 9 : dircloser dc(dir);
62 : : while (true) {
63 : 69 : errno = 0;
64 [ + - ]: 69 : struct dirent * entry = readdir(dir);
65 [ + + ]: 69 : if (entry == NULL) {
66 [ + - ]: 9 : if (errno == 0)
67 : 9 : break;
68 [ # # ][ # # ]: 0 : throw Xapian::DatabaseError("Cannot read entry from directory at '" + dirname + "'", errno);
[ # # ]
69 : : }
70 [ + - ]: 60 : string name(entry->d_name);
71 [ + - ][ + + ]: 60 : if (name == "." || name == "..")
[ + - ][ + + ]
[ + + ]
72 : 18 : continue;
73 [ + - ][ + - ]: 42 : if (unlink((dirname + "/" + name).c_str())) {
[ - + ]
74 [ # # ][ # # ]: 60 : throw Xapian::DatabaseError("Cannot remove file '" + string(entry->d_name) + "'", errno);
[ # # ][ # # ]
[ + + ]
75 : : }
76 : 69 : }
77 : : }
78 [ - + ]: 9 : if (rmdir(dirname.c_str())) {
79 [ # # ][ # # ]: 0 : throw Xapian::DatabaseError("Cannot remove directory '" + dirname + "'", errno);
[ # # ]
80 : : }
81 : : }
82 : :
83 : : #ifdef __WIN32__
84 : : /// Return true iff a path starts with a drive letter.
85 : : static bool
86 : : has_drive(const string &path)
87 : : {
88 : : return (path.size() >= 2 && path[1] == ':');
89 : : }
90 : :
91 : : /// Return true iff path is a UNCW path.
92 : : static bool
93 : : uncw_path(const string & path)
94 : : {
95 : : return (path.size() >= 4 && memcmp(path.data(), "\\\\?\\", 4) == 0);
96 : : }
97 : :
98 : : static inline bool slash(char ch)
99 : : {
100 : : return ch == '/' || ch == '\\';
101 : : }
102 : : #endif
103 : :
104 : : void
105 : 966 : resolve_relative_path(string & path, const string & base)
106 : : {
107 : : #ifndef __WIN32__
108 [ + - ][ + + ]: 966 : if (path.empty() || path[0] != '/') {
[ + + ]
109 : : // path is relative.
110 : 954 : string::size_type last_slash = base.rfind('/');
111 [ + + ]: 954 : if (last_slash != string::npos)
112 : 954 : path.insert(0, base, 0, last_slash + 1);
113 : : }
114 : : #else
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]));
120 : :
121 : : if (absolute) {
122 : : // If path is absolute and has a drive specifier, just return it.
123 : : if (drive)
124 : : return;
125 : :
126 : : // If base has a drive specifier prepend that to path.
127 : : if (has_drive(base)) {
128 : : path.insert(0, base, 0, 2);
129 : : return;
130 : : }
131 : :
132 : : // If base has a UNC (\\SERVER\\VOLUME) or \\?\ prefix, prepend that
133 : : // to path.
134 : : if (uncw_path(base)) {
135 : : string::size_type sl = 0;
136 : : if (base.size() >= 7 && memcmp(base.data() + 5, ":\\", 2) == 0) {
137 : : // "\\?\X:\"
138 : : sl = 6;
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);
145 : : }
146 : : if (sl) {
147 : : // With the \\?\ prefix, '/' isn't recognised so change it
148 : : // to '\' in path.
149 : : string::iterator i;
150 : : for (i = path.begin(); i != path.end(); ++i) {
151 : : if (*i == '/')
152 : : *i = '\\';
153 : : }
154 : : path.insert(0, base, 0, sl);
155 : : }
156 : : } else if (base.size() >= 5 && slash(base[0]) && slash(base[1])) {
157 : : // Handle UNC base.
158 : : string::size_type sl = base.find_first_of("/\\", 2);
159 : : if (sl != string::npos) {
160 : : sl = base.find_first_of("/\\", sl + 1);
161 : : path.insert(0, base, 0, sl);
162 : : }
163 : : }
164 : : return;
165 : : }
166 : :
167 : : // path is relative, so if it has no drive specifier or the same drive
168 : : // specifier as base, then we want to qualify it using base.
169 : : bool base_drive = has_drive(base);
170 : : if (!drive || (base_drive && (path[0] | 32) == (base[0] | 32))) {
171 : : string::size_type last_slash = base.find_last_of("/\\");
172 : : if (last_slash == string::npos && !drive && base_drive)
173 : : last_slash = 1;
174 : : if (last_slash != string::npos) {
175 : : string::size_type b = (drive && base_drive ? 2 : 0);
176 : : if (uncw_path(base)) {
177 : : // With the \\?\ prefix, '/' isn't recognised so change it
178 : : // to '\' in path.
179 : : string::iterator i;
180 : : for (i = path.begin(); i != path.end(); ++i) {
181 : : if (*i == '/')
182 : : *i = '\\';
183 : : }
184 : : }
185 : : path.insert(b, base, b, last_slash + 1 - b);
186 : : }
187 : : }
188 : : #endif
189 : 966 : }
|