monotone

monotone Mtn Source Tree

Root/file_io.cc

1// -*- mode: C++; c-file-style: "gnu"; indent-tabs-mode: nil -*-
2// copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com>
3// all rights reserved.
4// licensed to the public under the terms of the GNU GPL (>= 2)
5// see the file COPYING for details
6
7#include <iostream>
8#include <fstream>
9#include <boost/filesystem/path.hpp>
10#include <boost/filesystem/operations.hpp>
11#include <boost/filesystem/convenience.hpp>
12#include <boost/filesystem/exception.hpp>
13
14#include "botan/botan.h"
15
16#include "file_io.hh"
17#include "lua.hh"
18#include "sanity.hh"
19#include "transforms.hh"
20#include "platform.hh"
21
22// this file deals with talking to the filesystem, loading and
23// saving files.
24
25using namespace std;
26
27void
28assert_path_is_nonexistent(any_path const & path)
29{
30 I(get_path_status(path) == path::nonexistent);
31}
32
33void
34assert_path_is_file(any_path const & path)
35{
36 I(get_path_status(path) == path::file);
37}
38
39void
40assert_path_is_directory(any_path const & path)
41{
42 I(get_path_status(path) == path::directory);
43}
44
45void
46require_path_is_nonexistent(any_path const & path,
47 boost::format const & message)
48{
49 N(!path_exists(path), message);
50}
51
52void
53require_path_is_file(any_path const & path,
54 boost::format const & message_if_nonexistent,
55 boost::format const & message_if_directory)
56{
57 switch (get_path_status(path))
58 {
59 case path::nonexistent:
60 N(false, message_if_nonexistent);
61 break;
62 case path::file:
63 return;
64 case path::directory:
65 N(false, message_if_directory);
66 break;
67 }
68}
69
70void
71require_path_is_directory(any_path const & path,
72 boost::format const & message_if_nonexistent,
73 boost::format const & message_if_file)
74{
75 switch (get_path_status(path))
76 {
77 case path::nonexistent:
78 N(false, message_if_nonexistent);
79 break;
80 case path::file:
81 N(false, message_if_file);
82 case path::directory:
83 return;
84 break;
85 }
86}
87
88bool
89path_exists(any_path const & p)
90{
91 return get_path_status(p) != path::nonexistent;
92}
93
94bool
95directory_exists(any_path const & p)
96{
97 return get_path_status(p) == path::directory;
98}
99
100bool
101file_exists(any_path const & p)
102{
103 return get_path_status(p) == path::file;
104}
105
106bool
107ident_existing_file(file_path const & p, file_id & ident, lua_hooks & lua)
108{
109 switch (get_path_status(p))
110 {
111 case path::nonexistent:
112 return false;
113 case path::file:
114 break;
115 case path::directory:
116 W(F("expected file '%s', but it is a directory.") % p);
117 return false;
118 }
119
120 hexenc<id> id;
121 calculate_ident(p, id, lua);
122 ident = file_id(id);
123
124 return true;
125}
126
127static bool did_char_is_binary_init;
128static bool char_is_binary[256];
129
130void
131set_char_is_binary(char c, bool is_binary)
132{
133 char_is_binary[static_cast<uint8_t>(c)] = is_binary;
134}
135
136static void
137init_char_is_binary()
138{
139 // these do not occur in ASCII text files
140 // FIXME: this heuristic is (a) crap and (b) hardcoded. fix both these.
141 // Should be calling a lua hook here that can use set_char_is_binary()
142 // That will at least fix (b)
143 string nontext_chars("\x01\x02\x03\x04\x05\x06\x0e\x0f"
144 "\x10\x11\x12\x13\x14\x15\x16\x17\x18"
145 "\x19\x1a\x1c\x1d\x1e\x1f");
146 set_char_is_binary('\0',true);
147 for(size_t i = 0; i < nontext_chars.size(); ++i)
148 {
149 set_char_is_binary(nontext_chars[i], true);
150 }
151}
152
153bool guess_binary(string const & s)
154{
155 if (did_char_is_binary_init == false)
156 {
157 init_char_is_binary();
158 }
159
160 for (size_t i = 0; i < s.size(); ++i)
161 {
162 if (char_is_binary[ static_cast<uint8_t>(s[i]) ])
163 return true;
164 }
165 return false;
166}
167
168static fs::path
169mkdir(any_path const & p)
170{
171 return fs::path(p.as_external(), fs::native);
172}
173
174void
175mkdir_p(any_path const & p)
176{
177 try
178 {
179 fs::create_directories(mkdir(p));
180 }
181 catch (fs::filesystem_error & err)
182 {
183 // check for this case first, because in this case, the next line will
184 // print "could not create directory: Success". Which is unhelpful.
185 E(get_path_status(p) != path::file,
186 F("could not create directory '%s'\nit is a file") % p);
187 E(false,
188 F("could not create directory '%s'\n%s")
189 % err.path1().native_directory_string() % strerror(err.native_error()));
190 }
191 require_path_is_directory(p,
192 F("could not create directory '%s'") % p,
193 F("could not create directory '%s'\nit is a file") % p);
194}
195
196void
197make_dir_for(any_path const & p)
198{
199 fs::path tmp(p.as_external(), fs::native);
200 if (tmp.has_branch_path())
201 {
202 fs::path dir = tmp.branch_path();
203 fs::create_directories(dir);
204 N(fs::exists(dir) && fs::is_directory(dir),
205 F("failed to create directory '%s' for '%s'") % dir.string() % p);
206 }
207}
208
209void
210delete_file(any_path const & p)
211{
212 require_path_is_file(p,
213 F("file to delete '%s' does not exist") % p,
214 F("file to delete, '%s', is not a file but a directory") % p);
215 fs::remove(mkdir(p));
216}
217
218void
219delete_dir_recursive(any_path const & p)
220{
221 require_path_is_directory(p,
222 F("directory to delete, '%s', does not exist") % p,
223 F("directory to delete, '%s', is a file") % p);
224 fs::remove_all(mkdir(p));
225}
226
227void
228move_file(any_path const & old_path,
229 any_path const & new_path)
230{
231 require_path_is_file(old_path,
232 F("rename source file '%s' does not exist") % old_path,
233 F("rename source file '%s' is a directory "
234 "-- bug in monotone?") % old_path);
235 require_path_is_nonexistent(new_path,
236 F("rename target '%s' already exists") % new_path);
237 fs::rename(mkdir(old_path), mkdir(new_path));
238}
239
240void
241move_dir(any_path const & old_path,
242 any_path const & new_path)
243{
244 require_path_is_directory(old_path,
245 F("rename source dir '%s' does not exist") % old_path,
246 F("rename source dir '%s' is a file "
247 "-- bug in monotone?") % old_path);
248 require_path_is_nonexistent(new_path,
249 F("rename target '%s' already exists") % new_path);
250 fs::rename(mkdir(old_path), mkdir(new_path));
251}
252
253void
254move_path(any_path const & old_path,
255 any_path const & new_path)
256{
257 switch (get_path_status(old_path))
258 {
259 case path::nonexistent:
260 N(false, F("rename source path '%s' does not exist") % old_path);
261 break;
262 case path::file:
263 move_file(old_path, new_path);
264 break;
265 case path::directory:
266 move_dir(old_path, new_path);
267 break;
268 }
269}
270
271void
272read_data(any_path const & p, data & dat)
273{
274 require_path_is_file(p,
275 F("file %s does not exist") % p,
276 F("file %s cannot be read as data; it is a directory") % p);
277
278 ifstream file(p.as_external().c_str(),
279 ios_base::in | ios_base::binary);
280 N(file, F("cannot open file %s for reading") % p);
281 Botan::Pipe pipe;
282 pipe.start_msg();
283 file >> pipe;
284 pipe.end_msg();
285 dat = pipe.read_all_as_string();
286}
287
288void
289read_localized_data(file_path const & path,
290 data & dat,
291 lua_hooks & lua)
292{
293 string db_linesep, ext_linesep;
294 string db_charset, ext_charset;
295
296 bool do_lineconv = (lua.hook_get_linesep_conv(path, db_linesep, ext_linesep)
297 && db_linesep != ext_linesep);
298
299 bool do_charconv = (lua.hook_get_charset_conv(path, db_charset, ext_charset)
300 && db_charset != ext_charset);
301
302 data tdat;
303 read_data(path, tdat);
304
305 string tmp1, tmp2;
306 tmp2 = tdat();
307 if (do_charconv) {
308 tmp1 = tmp2;
309 charset_convert(ext_charset, db_charset, tmp1, tmp2);
310 }
311 if (do_lineconv) {
312 tmp1 = tmp2;
313 line_end_convert(db_linesep, tmp1, tmp2);
314 }
315 dat = tmp2;
316}
317
318void read_directory(system_path const & path,
319 std::vector<utf8> & files,
320 std::vector<utf8> & dirs)
321{
322 files.clear();
323 dirs.clear();
324 fs::directory_iterator ei;
325 for(fs::directory_iterator di(path.as_external());
326 di != ei; ++di)
327 {
328 fs::path entry = *di;
329 if (!fs::exists(entry)
330 || di->string() == "."
331 || di->string() == "..")
332 continue;
333
334 if (fs::is_directory(entry))
335 dirs.push_back(utf8(entry.leaf()));
336 else
337 files.push_back(utf8(entry.leaf()));
338 }
339}
340
341
342// This function can only be called once per run.
343static void
344read_data_stdin(data & dat)
345{
346 static bool have_consumed_stdin = false;
347 N(!have_consumed_stdin, F("Cannot read standard input multiple times"));
348 have_consumed_stdin = true;
349 Botan::Pipe pipe;
350 pipe.start_msg();
351 cin >> pipe;
352 pipe.end_msg();
353 dat = pipe.read_all_as_string();
354}
355
356void
357read_data_for_command_line(utf8 const & path, data & dat)
358{
359 if (path() == "-")
360 read_data_stdin(dat);
361 else
362 read_data(system_path(path), dat);
363}
364
365
366// FIXME: this is probably not enough brains to actually manage "atomic
367// filesystem writes". at some point you have to draw the line with even
368// trying, and I'm not sure it's really a strict requirement of this tool,
369// but you might want to make this code a bit tighter.
370
371
372static void
373write_data_impl(any_path const & p,
374 data const & dat,
375 any_path const & tmp)
376{
377 N(!directory_exists(p),
378 F("file '%s' cannot be overwritten as data; it is a directory") % p);
379
380 make_dir_for(p);
381
382 {
383 // data.tmp opens
384 ofstream file(tmp.as_external().c_str(),
385 ios_base::out | ios_base::trunc | ios_base::binary);
386 N(file, F("cannot open file %s for writing") % tmp);
387 Botan::Pipe pipe(new Botan::DataSink_Stream(file));
388 pipe.process_msg(dat());
389 // data.tmp closes
390 }
391
392 rename_clobberingly(tmp, p);
393}
394
395static void
396write_data_impl(any_path const & p,
397 data const & dat)
398{
399 // we write, non-atomically, to MT/data.tmp.
400 // nb: no mucking around with multiple-writer conditions. we're a
401 // single-user single-threaded program. you get what you paid for.
402 assert_path_is_directory(bookkeeping_root);
403 bookkeeping_path tmp = bookkeeping_root / (boost::format("data.tmp.%d") %
404 get_process_id()).str();
405 write_data_impl(p, dat, tmp);
406}
407
408void
409write_data(file_path const & path, data const & dat)
410{
411 write_data_impl(path, dat);
412}
413
414void
415write_data(bookkeeping_path const & path, data const & dat)
416{
417 write_data_impl(path, dat);
418}
419
420void
421write_localized_data(file_path const & path,
422 data const & dat,
423 lua_hooks & lua)
424{
425 string db_linesep, ext_linesep;
426 string db_charset, ext_charset;
427
428 bool do_lineconv = (lua.hook_get_linesep_conv(path, db_linesep, ext_linesep)
429 && db_linesep != ext_linesep);
430
431 bool do_charconv = (lua.hook_get_charset_conv(path, db_charset, ext_charset)
432 && db_charset != ext_charset);
433
434 string tmp1, tmp2;
435 tmp2 = dat();
436 if (do_lineconv) {
437 tmp1 = tmp2;
438 line_end_convert(ext_linesep, tmp1, tmp2);
439 }
440 if (do_charconv) {
441 tmp1 = tmp2;
442 charset_convert(db_charset, ext_charset, tmp1, tmp2);
443 }
444
445 write_data(path, data(tmp2));
446}
447
448void
449write_data(system_path const & path,
450 data const & data,
451 system_path const & tmpdir)
452{
453 write_data_impl(path, data, tmpdir / (boost::format("data.tmp.%d") %
454 get_process_id()).str());
455
456
457}
458
459tree_walker::~tree_walker() {}
460
461static void
462walk_tree_recursive(fs::path const & absolute,
463 fs::path const & relative,
464 tree_walker & walker)
465{
466 fs::directory_iterator ei;
467 for(fs::directory_iterator di(absolute);
468 di != ei; ++di)
469 {
470 fs::path entry = *di;
471 // the fs::native is necessary here, or it will bomb out on any paths
472 // that look at it funny. (E.g., rcs files with "," in the name.)
473 fs::path rel_entry = relative / fs::path(entry.leaf(), fs::native);
474
475 if (bookkeeping_path::is_bookkeeping_path(rel_entry.normalize().string()))
476 {
477 L(F("ignoring book keeping entry %s\n") % rel_entry.string());
478 continue;
479 }
480
481 if (!fs::exists(entry)
482 || di->string() == "."
483 || di->string() == "..")
484 ; // ignore
485 else if (fs::is_directory(entry))
486 walk_tree_recursive(entry, rel_entry, walker);
487 else
488 {
489 file_path p;
490 try
491 {
492 // FIXME: BUG: this screws up charsets
493 p = file_path_internal(rel_entry.normalize().string());
494 }
495 catch (std::runtime_error const & c)
496 {
497 W(F("caught runtime error %s constructing file path for %s\n")
498 % c.what() % rel_entry.string());
499 continue;
500 }
501 walker.visit_file(p);
502 }
503 }
504}
505
506// from some (safe) sub-entry of cwd
507void
508walk_tree(file_path const & path,
509 tree_walker & walker,
510 bool require_existing_path)
511{
512 if (path.empty())
513 {
514 walk_tree_recursive(fs::current_path(), fs::path(), walker);
515 return;
516 }
517
518 switch (get_path_status(path))
519 {
520 case path::nonexistent:
521 N(!require_existing_path, F("no such file or directory: '%s'") % path);
522 walker.visit_file(path);
523 break;
524 case path::file:
525 walker.visit_file(path);
526 break;
527 case path::directory:
528 walk_tree_recursive(system_path(path).as_external(),
529 path.as_external(),
530 walker);
531 break;
532 }
533}
534
535#ifdef BUILD_UNIT_TESTS
536#include "unit_tests.hh"
537
538void
539add_file_io_tests(test_suite * suite)
540{
541 I(suite);
542 // none, ATM.
543}
544
545#endif // BUILD_UNIT_TESTS

Archive Download this file

Branches

Tags

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