monotone

monotone Mtn Source Tree

Root/file_io.cc

1// copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com>
2// all rights reserved.
3// licensed to the public under the terms of the GNU GPL (>= 2)
4// see the file COPYING for details
5
6#include <stdio.h> // for rename(2)
7
8#include <boost/filesystem/path.hpp>
9#include <boost/filesystem/operations.hpp>
10#include <boost/filesystem/convenience.hpp>
11
12#include "cryptopp/filters.h"
13#include "cryptopp/files.h"
14
15#include "file_io.hh"
16#include "lua.hh"
17#include "sanity.hh"
18#include "transforms.hh"
19
20// this file deals with talking to the filesystem, loading and
21// saving files.
22
23using namespace std;
24
25string const book_keeping_dir("MT");
26
27
28#include <stdlib.h>
29#include <unistd.h>
30#include <pwd.h>
31#include <sys/types.h>
32
33void save_initial_path()
34{
35 fs::initial_path();
36}
37
38fs::path mkpath(string const & s)
39{
40 fs::path p(s, fs::native);
41 return p;
42}
43
44string get_homedir()
45{
46 char * home = getenv("HOME");
47 if (home != NULL)
48 return string(home);
49
50 struct passwd * pw = getpwuid(getuid());
51 N(pw != NULL, F("could not find home directory for uid %d") % getuid());
52 return string(pw->pw_dir);
53}
54
55
56static fs::path localized(string const & utf)
57{
58 fs::path tmp = mkpath(utf), ret;
59 for (fs::path::iterator i = tmp.begin(); i != tmp.end(); ++i)
60 {
61 external ext;
62 utf8_to_system(utf8(*i), ext);
63 ret /= mkpath(ext());
64 }
65 return ret;
66}
67
68string absolutify(string const & path)
69{
70 fs::path tmp = mkpath(path);
71 if (! tmp.has_root_path())
72 tmp = fs::initial_path() / tmp;
73 I(tmp.has_root_path());
74 return tmp.string();
75}
76
77string tilde_expand(string const & path)
78{
79 fs::path tmp = mkpath(path);
80 fs::path::iterator i = tmp.begin();
81 if (i != tmp.end())
82 {
83 fs::path res;
84 if (*i == "~")
85{
86 res /= mkpath(get_homedir());
87 ++i;
88}
89 else if (i->size() > 1 && i->at(0) == '~')
90{
91 struct passwd * pw;
92 pw = getpwnam(i->substr(1).c_str());
93 N(pw != NULL, F("could not find home directory user %s") % i->substr(1));
94 res /= mkpath(string(pw->pw_dir));
95 ++i;
96}
97 while (i != tmp.end())
98res /= *i++;
99 return res.string();
100 }
101
102 return tmp.string();
103}
104
105bool book_keeping_file(fs::path const & p)
106{
107 using boost::filesystem::path;
108 for(path::iterator i = p.begin(); i != p.end(); ++i)
109 {
110 if (*i == book_keeping_dir)
111return true;
112 }
113 return false;
114}
115
116bool book_keeping_file(local_path const & p)
117{
118 if (p() == book_keeping_dir) return true;
119 if (*(mkpath(p()).begin()) == book_keeping_dir) return true;
120 return false;
121}
122
123bool directory_exists(local_path const & p)
124{
125 return fs::exists(localized(p())) &&
126 fs::is_directory(localized(p()));
127}
128bool file_exists(file_path const & p) { return fs::exists(localized(p())); }
129bool file_exists(local_path const & p) { return fs::exists(localized(p())); }
130
131void delete_file(local_path const & p) { fs::remove(localized(p())); }
132void delete_file(file_path const & p) { fs::remove(localized(p())); }
133
134void move_file(file_path const & old_path,
135 file_path const & new_path)
136{
137 fs::rename(localized(old_path()),
138 localized(new_path()));
139}
140
141void mkdir_p(local_path const & p)
142{
143 fs::create_directories(localized(p()));
144}
145
146void mkdir_p(file_path const & p)
147{
148 fs::create_directories(localized(p()));
149}
150
151void make_dir_for(file_path const & p) {
152 fs::path tmp = mkpath(p());
153 if (tmp.has_branch_path())
154 {
155 fs::create_directories(tmp.branch_path());
156 }
157}
158
159
160static void read_data_impl(fs::path const & p,
161 data & dat)
162{
163 if (!fs::exists(p))
164 throw oops("file '" + p.string() + "' does not exist");
165
166 if (fs::is_directory(p))
167 throw oops("file '" + p.string() + "' cannot be read as data; it is a directory");
168
169 ifstream file(p.string().c_str());
170 string in;
171 if (!file)
172 throw oops(string("cannot open file ") + p.string() + " for reading");
173 CryptoPP::FileSource f(file, true, new CryptoPP::StringSink(in));
174 dat = in;
175}
176
177void read_data(local_path const & path, data & dat)
178{ read_data_impl(localized(path()), dat); }
179
180void read_data(file_path const & path, data & dat)
181{ read_data_impl(localized(path()), dat); }
182
183void read_data(local_path const & path,
184 base64< gzip<data> > & dat)
185{
186 data data_plain;
187 read_data_impl(localized(path()), data_plain);
188 gzip<data> data_compressed;
189 base64< gzip<data> > data_encoded;
190 encode_gzip(data_plain, data_compressed);
191 encode_base64(data_compressed, dat);
192}
193
194void read_data(file_path const & path, base64< gzip<data> > & dat)
195{ read_data(local_path(path()), dat); }
196
197void read_localized_data(file_path const & path,
198 base64< gzip<data> > & dat,
199 lua_hooks & lua)
200{
201 data data_plain;
202 read_localized_data(path, data_plain, lua);
203 gzip<data> data_compressed;
204 base64< gzip<data> > data_encoded;
205 encode_gzip(data_plain, data_compressed);
206 encode_base64(data_compressed, dat);
207}
208
209void read_localized_data(file_path const & path,
210 data & dat,
211 lua_hooks & lua)
212{
213 string db_linesep, ext_linesep;
214 string db_charset, ext_charset;
215
216 bool do_lineconv = (lua.hook_get_linesep_conv(path, db_linesep, ext_linesep)
217 && db_linesep != ext_linesep);
218
219 bool do_charconv = (lua.hook_get_charset_conv(path, db_charset, ext_charset)
220 && db_charset != ext_charset);
221
222 data tdat;
223 read_data(path, tdat);
224
225 string tmp1, tmp2;
226 tmp2 = tdat();
227 if (do_charconv)
228 charset_convert(ext_charset, db_charset, tmp1, tmp2);
229 tmp1 = tmp2;
230 if (do_lineconv)
231 line_end_convert(db_linesep, tmp1, tmp2);
232 dat = tmp2;
233}
234
235
236// FIXME: this is probably not enough brains to actually manage "atomic
237// filesystem writes". at some point you have to draw the line with even
238// trying, and I'm not sure it's really a strict requirement of this tool,
239// but you might want to make this code a bit tighter.
240
241
242static void write_data_impl(fs::path const & p,
243 data const & dat)
244{
245 if (fs::exists(p) && fs::is_directory(p))
246 throw oops("file '" + p.string() + "' cannot be over-written as data; it is a directory");
247
248 fs::create_directories(p.branch_path().string());
249
250 // we write, non-atomically, to MT/data.tmp.
251 // nb: no mucking around with multiple-writer conditions. we're a
252 // single-user single-threaded program. you get what you paid for.
253 fs::path mtdir = mkpath(book_keeping_dir);
254 fs::create_directories(mtdir);
255 fs::path tmp = mtdir / "data.tmp";
256
257 {
258 // data.tmp opens
259 ofstream file(tmp.string().c_str());
260 if (!file)
261 throw oops(string("cannot open file ") + tmp.string() + " for writing");
262 CryptoPP::StringSource s(dat(), true, new CryptoPP::FileSink(file));
263 // data.tmp closes
264 }
265
266 // god forgive my portability sins
267 N(rename(tmp.string().c_str(), p.string().c_str()) == 0,
268 F("rename of %s to %s failed") % tmp.string() % p.string());
269}
270
271void write_data(local_path const & path, data const & dat)
272{
273 write_data_impl(localized(path()), dat);
274}
275
276void write_data(file_path const & path, data const & dat)
277{
278 write_data_impl(localized(path()), dat);
279}
280
281void write_localized_data(file_path const & path,
282 data const & dat,
283 lua_hooks & lua)
284{
285 string db_linesep, ext_linesep;
286 string db_charset, ext_charset;
287
288 bool do_lineconv = (lua.hook_get_linesep_conv(path, db_linesep, ext_linesep)
289 && db_linesep != ext_linesep);
290
291 bool do_charconv = (lua.hook_get_charset_conv(path, db_charset, ext_charset)
292 && db_charset != ext_charset);
293
294 string tmp1, tmp2;
295 tmp2 = dat();
296 if (do_lineconv)
297 line_end_convert(ext_linesep, tmp1, tmp2);
298 tmp1 = tmp2;
299 if (do_charconv)
300 charset_convert(db_charset, ext_charset, tmp1, tmp2);
301
302 write_data(path, data(tmp2));
303}
304
305void write_localized_data(file_path const & path,
306 base64< gzip<data> > const & dat,
307 lua_hooks & lua)
308{
309 gzip<data> data_decoded;
310 data data_decompressed;
311 decode_base64(dat, data_decoded);
312 decode_gzip(data_decoded, data_decompressed);
313 write_localized_data(path, data_decompressed, lua);
314}
315
316void write_data(local_path const & path,
317base64< gzip<data> > const & dat)
318{
319 gzip<data> data_decoded;
320 data data_decompressed;
321 decode_base64(dat, data_decoded);
322 decode_gzip(data_decoded, data_decompressed);
323 write_data_impl(localized(path()), data_decompressed);
324}
325
326void write_data(file_path const & path,
327base64< gzip<data> > const & dat)
328{
329 write_data(local_path(path()), dat);
330}
331
332
333tree_walker::~tree_walker() {}
334
335static void walk_tree_recursive(fs::path const & absolute,
336fs::path const & relative,
337tree_walker & walker)
338{
339 fs::directory_iterator ei;
340 for(fs::directory_iterator di(absolute);
341 di != ei; ++di)
342 {
343 fs::path entry = mkpath(di->string());
344 fs::path rel_entry = relative / mkpath(entry.leaf());
345
346 if (book_keeping_file (entry))
347continue;
348
349 if (fs::is_directory(entry))
350walk_tree_recursive(entry, rel_entry, walker);
351 else
352{
353 file_path p;
354 try
355 {
356 p = file_path(rel_entry.string());
357 }
358 catch (std::runtime_error const & c)
359 {
360 L(F("caught runtime error %s constructing file path for %s\n")
361% c.what() % rel_entry.string());
362 continue;
363 }
364 walker.visit_file(p);
365}
366 }
367}
368
369// from some (safe) sub-entry of cwd
370void walk_tree(file_path const & path,
371 tree_walker & walker)
372{
373 if (! fs::is_directory(localized(path())))
374 walker.visit_file(path);
375 else
376 {
377 fs::path root(localized(fs::current_path().string()));
378 fs::path rel(localized(path()));
379 walk_tree_recursive(root / rel, rel, walker);
380 }
381}
382
383// from cwd (nb: we can't describe cwd as a file_path)
384void walk_tree(tree_walker & walker)
385{
386 walk_tree_recursive(fs::current_path(), fs::path(), walker);
387}
388
389
390#ifdef BUILD_UNIT_TESTS
391#include "unit_tests.hh"
392
393static void test_book_keeping_file()
394{
395 // positive tests
396 BOOST_CHECK(book_keeping_file(local_path("MT")));
397 BOOST_CHECK(book_keeping_file(local_path("MT/foo")));
398 BOOST_CHECK(book_keeping_file(local_path("MT/foo/bar/baz")));
399 // negative tests
400 BOOST_CHECK( ! book_keeping_file(local_path("safe")));
401 BOOST_CHECK( ! book_keeping_file(local_path("safe/path")));
402 BOOST_CHECK( ! book_keeping_file(local_path("safe/path/MT")));
403 BOOST_CHECK( ! book_keeping_file(local_path("MTT")));
404}
405
406void add_file_io_tests(test_suite * suite)
407{
408 I(suite);
409 suite->add(BOOST_TEST_CASE(&test_book_keeping_file));
410}
411
412#endif // BUILD_UNIT_TESTS

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status