monotone

monotone Mtn Source Tree

Root/file_io.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include <iostream>
11#include <fstream>
12#include <boost/filesystem/path.hpp>
13#include <boost/filesystem/operations.hpp>
14#include <boost/filesystem/convenience.hpp>
15#include <boost/filesystem/exception.hpp>
16
17#include "botan/botan.h"
18
19#include "file_io.hh"
20#include "sanity.hh"
21#include "simplestring_xform.hh"
22#include "charset.hh"
23#include "platform-wrapped.hh"
24#include "numeric_vocab.hh"
25
26
27// Parts of boost::filesystem change in 1.34 . One particular
28// difference is that some exceptions are different now.
29
30#include <boost/version.hpp>
31
32#if BOOST_VERSION < 103400
33# define FS_ERROR fs::filesystem_error
34# define FS_ERROR_SYSTEM native_error
35#else
36# define FS_ERROR fs::filesystem_path_error
37# define FS_ERROR_SYSTEM system_error
38#endif
39
40
41
42
43
44// this file deals with talking to the filesystem, loading and
45// saving files.
46
47using std::cin;
48using std::ifstream;
49using std::ios_base;
50using std::ofstream;
51using std::runtime_error;
52using std::string;
53using std::vector;
54
55void
56assert_path_is_nonexistent(any_path const & path)
57{
58 I(get_path_status(path) == path::nonexistent);
59}
60
61void
62assert_path_is_file(any_path const & path)
63{
64 I(get_path_status(path) == path::file);
65}
66
67void
68assert_path_is_directory(any_path const & path)
69{
70 I(get_path_status(path) == path::directory);
71}
72
73void
74require_path_is_nonexistent(any_path const & path,
75 i18n_format const & message)
76{
77 N(!path_exists(path), message);
78}
79
80void
81require_path_is_file(any_path const & path,
82 i18n_format const & message_if_nonexistent,
83 i18n_format const & message_if_directory)
84{
85 switch (get_path_status(path))
86 {
87 case path::nonexistent:
88 N(false, message_if_nonexistent);
89 break;
90 case path::file:
91 return;
92 case path::directory:
93 N(false, message_if_directory);
94 break;
95 }
96}
97
98void
99require_path_is_directory(any_path const & path,
100 i18n_format const & message_if_nonexistent,
101 i18n_format const & message_if_file)
102{
103 switch (get_path_status(path))
104 {
105 case path::nonexistent:
106 N(false, message_if_nonexistent);
107 break;
108 case path::file:
109 N(false, message_if_file);
110 case path::directory:
111 return;
112 break;
113 }
114}
115
116bool
117path_exists(any_path const & p)
118{
119 return get_path_status(p) != path::nonexistent;
120}
121
122bool
123directory_exists(any_path const & p)
124{
125 return get_path_status(p) == path::directory;
126}
127
128bool
129file_exists(any_path const & p)
130{
131 return get_path_status(p) == path::file;
132}
133
134bool
135directory_empty(any_path const & path)
136{
137 vector<utf8> files;
138 vector<utf8> subdirs;
139
140 read_directory(path, files, subdirs);
141
142 return files.empty() && subdirs.empty();
143}
144
145static bool did_char_is_binary_init;
146static bool char_is_binary[256];
147
148static void
149set_char_is_binary(char c, bool is_binary)
150{
151 char_is_binary[static_cast<uint8_t>(c)] = is_binary;
152}
153
154static void
155init_char_is_binary()
156{
157 // these do not occur in ASCII text files
158 // FIXME: this heuristic is (a) crap and (b) hardcoded. fix both these.
159 // Should be calling a lua hook here that can use set_char_is_binary()
160 // That will at least fix (b)
161 string nontext_chars("\x01\x02\x03\x04\x05\x06\x0e\x0f"
162 "\x10\x11\x12\x13\x14\x15\x16\x17\x18"
163 "\x19\x1a\x1c\x1d\x1e\x1f");
164 set_char_is_binary('\0', true);
165 for(size_t i = 0; i < nontext_chars.size(); ++i)
166 {
167 set_char_is_binary(nontext_chars[i], true);
168 }
169}
170
171bool guess_binary(string const & s)
172{
173 if (did_char_is_binary_init == false)
174 {
175 init_char_is_binary();
176 did_char_is_binary_init = true;
177 }
178
179 for (size_t i = 0; i < s.size(); ++i)
180 {
181 if (char_is_binary[ static_cast<uint8_t>(s[i]) ])
182 return true;
183 }
184 return false;
185}
186
187static fs::path
188mkdir(any_path const & p)
189{
190 return fs::path(p.as_external(), fs::native);
191}
192
193void
194mkdir_p(any_path const & p)
195{
196 try
197 {
198 fs::create_directories(mkdir(p));
199 }
200 catch (FS_ERROR & err)
201 {
202 // check for this case first, because in this case, the next line will
203 // print "could not create directory: Success". Which is unhelpful.
204 E(get_path_status(p) != path::file,
205 F("could not create directory '%s'\nit is a file") % p);
206 E(false,
207 F("could not create directory '%s'\n%s")
208 % err.path1().native_directory_string() % os_strerror(err.FS_ERROR_SYSTEM()));
209 }
210 require_path_is_directory(p,
211 F("could not create directory '%s'") % p,
212 F("could not create directory '%s'\nit is a file") % p);
213}
214
215void
216make_dir_for(any_path const & p)
217{
218 fs::path tmp(p.as_external(), fs::native);
219 if (tmp.has_branch_path())
220 {
221 fs::path dir = tmp.branch_path();
222 fs::create_directories(dir);
223 N(fs::exists(dir) && fs::is_directory(dir),
224 F("failed to create directory '%s' for '%s'") % dir.string() % p);
225 }
226}
227
228static void
229do_shallow_deletion_with_sane_error_message(any_path const & p)
230{
231 fs::path fp = mkdir(p);
232 try
233 {
234 fs::remove(fp);
235 }
236 catch (FS_ERROR & err)
237 {
238 E(false, F("could not remove '%s'\n%s")
239 % err.path1().native_directory_string()
240 % os_strerror(err.FS_ERROR_SYSTEM()));
241 }
242}
243
244void
245delete_file(any_path const & p)
246{
247 require_path_is_file(p,
248 F("file to delete '%s' does not exist") % p,
249 F("file to delete, '%s', is not a file but a directory") % p);
250 do_shallow_deletion_with_sane_error_message(p);
251}
252
253void
254delete_dir_shallow(any_path const & p)
255{
256 require_path_is_directory(p,
257 F("directory to delete '%s' does not exist") % p,
258 F("directory to delete, '%s', is not a directory but a file") % p);
259 do_shallow_deletion_with_sane_error_message(p);
260}
261
262void
263delete_file_or_dir_shallow(any_path const & p)
264{
265 N(path_exists(p), F("object to delete, '%s', does not exist") % p);
266 do_shallow_deletion_with_sane_error_message(p);
267}
268
269void
270delete_dir_recursive(any_path const & p)
271{
272 require_path_is_directory(p,
273 F("directory to delete, '%s', does not exist") % p,
274 F("directory to delete, '%s', is a file") % p);
275 fs::remove_all(mkdir(p));
276}
277
278void
279move_file(any_path const & old_path,
280 any_path const & new_path)
281{
282 require_path_is_file(old_path,
283 F("rename source file '%s' does not exist") % old_path,
284 F("rename source file '%s' is a directory "
285 "-- bug in monotone?") % old_path);
286 require_path_is_nonexistent(new_path,
287 F("rename target '%s' already exists") % new_path);
288 fs::rename(mkdir(old_path), mkdir(new_path));
289}
290
291void
292move_dir(any_path const & old_path,
293 any_path const & new_path)
294{
295 require_path_is_directory(old_path,
296 F("rename source dir '%s' does not exist") % old_path,
297 F("rename source dir '%s' is a file "
298 "-- bug in monotone?") % old_path);
299 require_path_is_nonexistent(new_path,
300 F("rename target '%s' already exists") % new_path);
301 fs::rename(mkdir(old_path), mkdir(new_path));
302}
303
304void
305move_path(any_path const & old_path,
306 any_path const & new_path)
307{
308 switch (get_path_status(old_path))
309 {
310 case path::nonexistent:
311 N(false, F("rename source path '%s' does not exist") % old_path);
312 break;
313 case path::file:
314 move_file(old_path, new_path);
315 break;
316 case path::directory:
317 move_dir(old_path, new_path);
318 break;
319 }
320}
321
322void
323read_data(any_path const & p, data & dat)
324{
325 require_path_is_file(p,
326 F("file %s does not exist") % p,
327 F("file %s cannot be read as data; it is a directory") % p);
328
329 ifstream file(p.as_external().c_str(),
330 ios_base::in | ios_base::binary);
331 N(file, F("cannot open file %s for reading") % p);
332 Botan::Pipe pipe;
333 pipe.start_msg();
334 file >> pipe;
335 pipe.end_msg();
336 dat = data(pipe.read_all_as_string());
337}
338
339void read_directory(any_path const & path,
340 vector<utf8> & files,
341 vector<utf8> & dirs)
342{
343 files.clear();
344 dirs.clear();
345 fs::directory_iterator ei;
346 fs::path native_path = fs::path(system_path(path).as_external(), fs::native);
347 for (fs::directory_iterator di(native_path);
348 di != ei; ++di)
349 {
350 fs::path entry = *di;
351 if (!fs::exists(entry)
352 || entry.string() == "."
353 || entry.string() == "..")
354 continue;
355
356 // FIXME: BUG: this screws up charsets (assumes blindly that the fs is
357 // utf8)
358 if (fs::is_directory(entry))
359 dirs.push_back(utf8(entry.leaf()));
360 else
361 files.push_back(utf8(entry.leaf()));
362 }
363}
364
365
366// This function can only be called once per run.
367void
368read_data_stdin(data & dat)
369{
370 static bool have_consumed_stdin = false;
371 N(!have_consumed_stdin, F("Cannot read standard input multiple times"));
372 have_consumed_stdin = true;
373 Botan::Pipe pipe;
374 pipe.start_msg();
375 cin >> pipe;
376 pipe.end_msg();
377 dat = data(pipe.read_all_as_string());
378}
379
380void
381read_data_for_command_line(utf8 const & path, data & dat)
382{
383 if (path() == "-")
384 read_data_stdin(dat);
385 else
386 read_data(system_path(path), dat);
387}
388
389
390// FIXME: this is probably not enough brains to actually manage "atomic
391// filesystem writes". at some point you have to draw the line with even
392// trying, and I'm not sure it's really a strict requirement of this tool,
393// but you might want to make this code a bit tighter.
394
395
396static void
397write_data_impl(any_path const & p,
398 data const & dat,
399 any_path const & tmp)
400{
401 N(!directory_exists(p),
402 F("file '%s' cannot be overwritten as data; it is a directory") % p);
403
404 make_dir_for(p);
405
406 {
407 // data.tmp opens
408 ofstream file(tmp.as_external().c_str(),
409 ios_base::out | ios_base::trunc | ios_base::binary);
410 N(file, F("cannot open file %s for writing") % tmp);
411 Botan::Pipe pipe(new Botan::DataSink_Stream(file));
412 pipe.process_msg(dat());
413 // data.tmp closes
414 }
415
416 rename_clobberingly(tmp, p);
417}
418
419static void
420write_data_impl(any_path const & p,
421 data const & dat)
422{
423 // we write, non-atomically, to _MTN/data.tmp.
424 // nb: no mucking around with multiple-writer conditions. we're a
425 // single-user single-threaded program. you get what you paid for.
426 assert_path_is_directory(bookkeeping_root);
427 bookkeeping_path tmp = bookkeeping_root / (FL("data.tmp.%d") %
428 get_process_id()).str();
429 write_data_impl(p, dat, tmp);
430}
431
432void
433write_data(file_path const & path, data const & dat)
434{
435 write_data_impl(path, dat);
436}
437
438void
439write_data(bookkeeping_path const & path, data const & dat)
440{
441 write_data_impl(path, dat);
442}
443
444void
445write_data(system_path const & path,
446 data const & data,
447 system_path const & tmpdir)
448{
449 write_data_impl(path, data, tmpdir / (FL("data.tmp.%d") %
450 get_process_id()).str());
451}
452
453tree_walker::~tree_walker() {}
454
455static inline bool
456try_file_pathize(fs::path const & p, file_path & fp)
457{
458 try
459 {
460 // FIXME BUG: This has broken charset handling
461 fp = file_path_internal(p.string());
462 return true;
463 }
464 catch (runtime_error const & c)
465 {
466 // This arguably has broken charset handling too...
467 W(F("caught runtime error %s constructing file path for %s")
468 % c.what() % p.string());
469 return false;
470 }
471}
472
473static void
474walk_tree_recursive(fs::path const & absolute,
475 fs::path const & relative,
476 tree_walker & walker)
477{
478 system_path root(absolute.string());
479 vector<utf8> files, dirs;
480
481 read_directory(root, files, dirs);
482
483 // At this point, the directory iterator has gone out of scope, and its
484 // memory released. This is important, because it can allocate rather a
485 // bit of memory (especially on ReiserFS, see [1]; opendir uses the
486 // filesystem's blocksize as a clue how much memory to allocate). We used
487 // to recurse into subdirectories directly in the loop below; this left
488 // the memory describing _this_ directory pinned on the heap. Then our
489 // recursive call itself made another recursive call, etc., causing a huge
490 // spike in peak memory. By splitting the loop in half, we avoid this
491 // problem. By using read_directory instead of a directory_iterator above
492 // we hopefully make this all a bit more clear.
493 //
494 // [1] http://lkml.org/lkml/2006/2/24/215
495
496 for (vector<utf8>::const_iterator i = files.begin(); i != files.end(); ++i)
497 {
498 // the fs::native is necessary here, or it will bomb out on any paths
499 // that look at it funny. (E.g., rcs files with "," in the name.)
500 fs::path rel_entry = relative / fs::path((*i)(), fs::native);
501 rel_entry.normalize();
502
503 file_path p;
504 if (!try_file_pathize(rel_entry, p))
505 continue;
506 walker.visit_file(p);
507 }
508
509 for (vector<utf8>::const_iterator i = dirs.begin(); i != dirs.end(); ++i)
510 {
511 // the fs::native is necessary here, or it will bomb out on any paths
512 // that look at it funny. (E.g., rcs files with "," in the name.)
513 fs::path entry = absolute / fs::path((*i)(), fs::native);
514 fs::path rel_entry = relative / fs::path((*i)(), fs::native);
515 entry.normalize();
516 rel_entry.normalize();
517
518 // FIXME BUG: this utf8() cast is a total lie
519 if (bookkeeping_path::internal_string_is_bookkeeping_path(utf8(rel_entry.string())))
520 {
521 L(FL("ignoring book keeping entry %s") % rel_entry.string());
522 continue;
523 }
524
525 file_path p;
526 if (!try_file_pathize(rel_entry, p))
527 continue;
528 if (walker.visit_dir(p))
529 walk_tree_recursive(entry, rel_entry, walker);
530 }
531}
532
533bool
534tree_walker::visit_dir(file_path const & path)
535{
536 return true;
537}
538
539
540// from some (safe) sub-entry of cwd
541void
542walk_tree(file_path const & path,
543 tree_walker & walker)
544{
545 if (path.empty())
546 {
547 walk_tree_recursive(fs::current_path(), fs::path(), walker);
548 return;
549 }
550
551 switch (get_path_status(path))
552 {
553 case path::nonexistent:
554 N(false, F("no such file or directory: '%s'") % path);
555 break;
556 case path::file:
557 walker.visit_file(path);
558 break;
559 case path::directory:
560 if (walker.visit_dir(path))
561 walk_tree_recursive(system_path(path).as_external(),
562 path.as_external(),
563 walker);
564 break;
565 }
566}
567
568bool
569ident_existing_file(file_path const & p, file_id & ident)
570{
571 switch (get_path_status(p))
572 {
573 case path::nonexistent:
574 return false;
575 case path::file:
576 break;
577 case path::directory:
578 W(F("expected file '%s', but it is a directory.") % p);
579 return false;
580 }
581
582 hexenc<id> id;
583 calculate_ident(p, id);
584 ident = file_id(id);
585
586 return true;
587}
588
589void
590calculate_ident(file_path const & file,
591 hexenc<id> & ident)
592{
593 // no conversions necessary, use streaming form
594 // Best to be safe and check it isn't a dir.
595 assert_path_is_file(file);
596 Botan::Pipe p(new Botan::Hash_Filter("SHA-160"), new Botan::Hex_Encoder());
597 Botan::DataSource_Stream infile(file.as_external(), true);
598 p.process_msg(infile);
599
600 ident = hexenc<id>(lowercase(p.read_all_as_string()));
601}
602
603// Local Variables:
604// mode: C++
605// fill-column: 76
606// c-file-style: "gnu"
607// indent-tabs-mode: nil
608// End:
609// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

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