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
318
319// This function can only be called once per run.
320static void
321read_data_stdin(data & dat)
322{
323 static bool have_consumed_stdin = false;
324 N(!have_consumed_stdin, F("Cannot read standard input multiple times"));
325 have_consumed_stdin = true;
326 Botan::Pipe pipe;
327 pipe.start_msg();
328 cin >> pipe;
329 pipe.end_msg();
330 dat = pipe.read_all_as_string();
331}
332
333void
334read_data_for_command_line(utf8 const & path, data & dat)
335{
336 if (path() == "-")
337 read_data_stdin(dat);
338 else
339 read_data(system_path(path), dat);
340}
341
342
343// FIXME: this is probably not enough brains to actually manage "atomic
344// filesystem writes". at some point you have to draw the line with even
345// trying, and I'm not sure it's really a strict requirement of this tool,
346// but you might want to make this code a bit tighter.
347
348
349static void
350write_data_impl(any_path const & p,
351 data const & dat)
352{
353 N(!directory_exists(p),
354 F("file '%s' cannot be overwritten as data; it is a directory") % p);
355
356 make_dir_for(p);
357
358 // we write, non-atomically, to MT/data.tmp.
359 // nb: no mucking around with multiple-writer conditions. we're a
360 // single-user single-threaded program. you get what you paid for.
361 assert_path_is_directory(bookkeeping_root);
362 bookkeeping_path tmp = bookkeeping_root / "data.tmp";
363
364 {
365 // data.tmp opens
366 ofstream file(tmp.as_external().c_str(),
367 ios_base::out | ios_base::trunc | ios_base::binary);
368 N(file, F("cannot open file %s for writing") % tmp);
369 Botan::Pipe pipe(new Botan::DataSink_Stream(file));
370 pipe.process_msg(dat());
371 // data.tmp closes
372 }
373
374 if (path_exists(p))
375 N(fs::remove(mkdir(p)), F("removing %s failed") % p);
376 fs::rename(mkdir(tmp), mkdir(p));
377}
378
379void
380write_data(file_path const & path, data const & dat)
381{
382 write_data_impl(path, dat);
383}
384
385void
386write_data(bookkeeping_path const & path, data const & dat)
387{
388 write_data_impl(path, dat);
389}
390
391void
392write_localized_data(file_path const & path,
393 data const & dat,
394 lua_hooks & lua)
395{
396 string db_linesep, ext_linesep;
397 string db_charset, ext_charset;
398
399 bool do_lineconv = (lua.hook_get_linesep_conv(path, db_linesep, ext_linesep)
400 && db_linesep != ext_linesep);
401
402 bool do_charconv = (lua.hook_get_charset_conv(path, db_charset, ext_charset)
403 && db_charset != ext_charset);
404
405 string tmp1, tmp2;
406 tmp2 = dat();
407 if (do_lineconv) {
408 tmp1 = tmp2;
409 line_end_convert(ext_linesep, tmp1, tmp2);
410 }
411 if (do_charconv) {
412 tmp1 = tmp2;
413 charset_convert(db_charset, ext_charset, tmp1, tmp2);
414 }
415
416 write_data(path, data(tmp2));
417}
418
419tree_walker::~tree_walker() {}
420
421static void
422walk_tree_recursive(fs::path const & absolute,
423 fs::path const & relative,
424 tree_walker & walker)
425{
426 fs::directory_iterator ei;
427 for(fs::directory_iterator di(absolute);
428 di != ei; ++di)
429 {
430 fs::path entry = *di;
431 // the fs::native is necessary here, or it will bomb out on any paths
432 // that look at it funny. (E.g., rcs files with "," in the name.)
433 fs::path rel_entry = relative / fs::path(entry.leaf(), fs::native);
434
435 if (bookkeeping_path::is_bookkeeping_path(rel_entry.normalize().string()))
436 {
437 L(F("ignoring book keeping entry %s\n") % rel_entry.string());
438 continue;
439 }
440
441 if (!fs::exists(entry)
442 || di->string() == "."
443 || di->string() == "..")
444 ; // ignore
445 else if (fs::is_directory(entry))
446 walk_tree_recursive(entry, rel_entry, walker);
447 else
448 {
449 file_path p;
450 try
451 {
452 // FIXME: BUG: this screws up charsets
453 p = file_path_internal(rel_entry.normalize().string());
454 }
455 catch (std::runtime_error const & c)
456 {
457 W(F("caught runtime error %s constructing file path for %s\n")
458 % c.what() % rel_entry.string());
459 continue;
460 }
461 walker.visit_file(p);
462 }
463 }
464}
465
466// from some (safe) sub-entry of cwd
467void
468walk_tree(file_path const & path,
469 tree_walker & walker,
470 bool require_existing_path)
471{
472 if (path.empty())
473 {
474 walk_tree_recursive(fs::current_path(), fs::path(), walker);
475 return;
476 }
477
478 switch (get_path_status(path))
479 {
480 case path::nonexistent:
481 N(!require_existing_path, F("no such file or directory: '%s'") % path);
482 walker.visit_file(path);
483 break;
484 case path::file:
485 walker.visit_file(path);
486 break;
487 case path::directory:
488 walk_tree_recursive(system_path(path).as_external(),
489 path.as_external(),
490 walker);
491 break;
492 }
493}
494
495absolute_tree_walker::~absolute_tree_walker() {}
496
497static void
498walk_tree_recursive(fs::path const & absolute,
499 absolute_tree_walker & walker)
500{
501 fs::directory_iterator ei;
502 for(fs::directory_iterator di(absolute);
503 di != ei; ++di)
504 {
505 fs::path entry = *di;
506
507 if (!fs::exists(entry)
508 || di->string() == "."
509 || di->string() == "..")
510 ; // ignore
511 else if (fs::is_directory(entry))
512 walk_tree_recursive(entry, walker);
513 else
514 {
515 system_path p;
516 try
517 {
518 // FIXME: BUG: this screws up charsets
519 p = system_path(entry.normalize().string());
520 }
521 catch (std::runtime_error const & c)
522 {
523 W(F("caught runtime error %s constructing file path for %s\n")
524 % c.what() % entry.string());
525 continue;
526 }
527 walker.visit_file(p);
528 }
529 }
530}
531
532void
533walk_tree_absolute(system_path const & path,
534 absolute_tree_walker & walker,
535 bool require_existing_path)
536{
537 switch (get_path_status(path))
538 {
539 case path::nonexistent:
540 N(require_existing_path, F("no such file or directory") % path);
541 walker.visit_file(path);
542 break;
543 case path::file:
544 walker.visit_file(path);
545 break;
546 case path::directory:
547 walk_tree_recursive(mkdir(path), walker);
548 break;
549 }
550}
551
552#ifdef BUILD_UNIT_TESTS
553#include "unit_tests.hh"
554
555void
556add_file_io_tests(test_suite * suite)
557{
558 I(suite);
559 // none, ATM.
560}
561
562#endif // BUILD_UNIT_TESTS

Archive Download this file

Branches

Tags

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