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