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#ifndef WIN32
31 #include <pwd.h>
32#endif
33#include <sys/types.h>
34
35void
36save_initial_path()
37{
38 fs::initial_path();
39 L(F("initial path is %s\n") % fs::initial_path().string());
40}
41
42bool
43find_working_copy(fs::path const & search_root,
44 fs::path & working_copy_root,
45 fs::path & working_copy_restriction)
46{
47 fs::path bookdir = mkpath(book_keeping_dir);
48 fs::path current = fs::initial_path();
49 fs::path removed;
50 fs::path check = current / bookdir;
51
52 L(F("searching for '%s' directory with root '%s'\n")
53 % bookdir.string()
54 % search_root.string());
55
56 // nb: boost 1.32.0 has added operations::equivalent(path1, path2)
57 // and ==, !=, ... on paths which are probably better than
58 // native_directory_string comparisons used here temporarily
59
60 while ( current.native_directory_string()
61 != search_root.native_directory_string() &&
62 current.has_branch_path() &&
63 current.has_leaf() &&
64 !fs::exists(check))
65 {
66 L(F("'%s' not found in '%s' with '%s' removed\n")
67 % bookdir.string() % current.string() % removed.string());
68 removed = mkpath(current.leaf()) / removed;
69 current = current.branch_path();
70 check = current / bookdir;
71 }
72
73 L(F("search for '%s' ended at '%s' with '%s' removed\n")
74 % book_keeping_dir % current.string() % removed.string());
75
76 if (!fs::exists(check))
77 {
78 L(F("'%s' does not exist\n") % check.string());
79 return false;
80 }
81
82 if (!fs::is_directory(check))
83 {
84 L(F("'%s' is not a directory\n") % check.string());
85 return false;
86 }
87
88 // check for MT/. and MT/.. to see if mt dir is readable
89 if (!fs::exists(check / ".") || !fs::exists(check / ".."))
90 {
91 L(F("problems with '%s' (missing '.' or '..')\n") % check.string());
92 return false;
93 }
94
95 working_copy_root = current;
96 working_copy_restriction = removed;
97
98 return true;
99}
100
101fs::path
102mkpath(string const & s)
103{
104 fs::path p(s, fs::native);
105 return p;
106}
107
108string
109get_homedir()
110{
111#ifdef WIN32
112 // Windows is fun!
113 // See thread on monotone-devel:
114 // Message-Id: <20050221.182951.104117563.dalcolmo@vh-s.de>
115 // URL: http://lists.gnu.org/archive/html/monotone-devel/2005-02/msg00241.html
116 char * home;
117 L(F("Searching for home directory\n"));
118 // First try MONOTONE_HOME, to give people a way out in case the cruft below
119 // doesn't work for them.
120 home = getenv("MONOTONE_HOME");
121 if (home != NULL)
122 {
123 L(F("Home directory from MONOTONE_HOME\n"));
124 return string(home);
125 }
126 // If running under cygwin or mingw, try HOME next:
127 home = getenv("HOME");
128 char * ostype = getenv("OSTYPE");
129 if (home != NULL
130 && ostype != NULL
131 && (string(ostype) == "cygwin" || string(ostype) == "msys"))
132 {
133 L(F("Home directory from HOME\n"));
134 return string(home);
135 }
136 // Otherwise, try USERPROFILE:
137 home = getenv("USERPROFILE");
138 if (home != NULL)
139 {
140 L(F("Home directory from USERPROFILE\n"));
141 return string(home);
142 }
143 // Finally, if even that doesn't work (old version of Windows, I think?),
144 // try the HOMEDRIVE/HOMEPATH combo:
145 char * homedrive = getenv("HOMEDRIVE");
146 char * homepath = getenv("HOMEPATH");
147 if (homedrive != NULL && homepath != NULL)
148 {
149 L(F("Home directory from HOMEDRIVE+HOMEPATH\n"));
150 return string(homedrive) + string(homepath);
151 }
152 // And if things _still_ didn't work, give up.
153 N(false, F("could not find home directory (tried MONOTONE_HOME, HOME (if "
154 "cygwin/mingw), USERPROFILE, HOMEDRIVE/HOMEPATH"));
155#else
156 char * home = getenv("HOME");
157 if (home != NULL)
158 return string(home);
159
160 struct passwd * pw = getpwuid(getuid());
161 N(pw != NULL, F("could not find home directory for uid %d") % getuid());
162 return string(pw->pw_dir);
163#endif
164}
165
166
167static fs::path
168localized(string const & utf)
169{
170 fs::path tmp = mkpath(utf), ret;
171 for (fs::path::iterator i = tmp.begin(); i != tmp.end(); ++i)
172 {
173 external ext;
174 utf8_to_system(utf8(*i), ext);
175 ret /= mkpath(ext());
176 }
177 return ret;
178}
179
180string
181absolutify(string const & path)
182{
183 fs::path tmp = mkpath(path);
184 if (! tmp.has_root_path())
185 tmp = fs::current_path() / tmp;
186 I(tmp.has_root_path());
187#if BOOST_VERSION >= 103100
188 tmp = tmp.normalize();
189#endif
190 return tmp.string();
191}
192
193string
194tilde_expand(string const & path)
195{
196 fs::path tmp = mkpath(path);
197 fs::path::iterator i = tmp.begin();
198 if (i != tmp.end())
199 {
200 fs::path res;
201 if (*i == "~")
202 {
203 res /= mkpath(get_homedir());
204 ++i;
205 }
206 else if (i->size() > 1 && i->at(0) == '~')
207 {
208#ifdef WIN32
209 res /= mkpath(get_homedir());
210#else
211 struct passwd * pw;
212 pw = getpwnam(i->substr(1).c_str());
213 N(pw != NULL, F("could not find home directory for user %s") % i->substr(1));
214 res /= mkpath(string(pw->pw_dir));
215#endif
216 ++i;
217 }
218 while (i != tmp.end())
219 res /= mkpath(*i++);
220 return res.string();
221 }
222
223 return tmp.string();
224}
225
226static bool
227book_keeping_file(fs::path const & p)
228{
229 return *(p.begin()) == book_keeping_dir;
230}
231
232bool
233book_keeping_file(local_path const & p)
234{
235 if (p() == book_keeping_dir) return true;
236 if (*(mkpath(p()).begin()) == book_keeping_dir) return true;
237 return false;
238}
239
240bool
241directory_exists(local_path const & p)
242{
243 return fs::exists(localized(p())) &&
244 fs::is_directory(localized(p()));
245}
246
247bool
248directory_exists(file_path const & p)
249{
250 return fs::exists(localized(p())) &&
251 fs::is_directory(localized(p()));
252}
253
254bool
255file_exists(file_path const & p)
256{
257 return fs::exists(localized(p()));
258}
259
260bool
261file_exists(local_path const & p)
262{
263 return fs::exists(localized(p()));
264}
265
266void
267delete_file(local_path const & p)
268{
269 N(file_exists(p),
270 F("file to delete '%s' does not exist") % p);
271 fs::remove(localized(p()));
272}
273
274void
275delete_file(file_path const & p)
276{
277 N(file_exists(p),
278 F("file to delete '%s' does not exist") % p);
279 fs::remove(localized(p()));
280}
281
282void
283delete_dir_recursive(file_path const & p)
284{
285 N(directory_exists(p),
286 F("directory to delete '%s' does not exist") % p);
287 fs::remove_all(localized(p()));
288}
289
290void
291delete_dir_recursive(local_path const & p)
292{
293 N(directory_exists(p),
294 F("directory to delete '%s' does not exist") % p);
295 fs::remove_all(localized(p()));
296}
297
298void
299move_file(file_path const & old_path,
300 file_path const & new_path)
301{
302 N(file_exists(old_path),
303 F("rename source file '%s' does not exist") % old_path);
304 N(! file_exists(new_path),
305 F("rename target file '%s' already exists") % new_path);
306 fs::rename(localized(old_path()),
307 localized(new_path()));
308}
309
310void
311move_file(local_path const & old_path,
312 local_path const & new_path)
313{
314 N(file_exists(old_path),
315 F("rename source file '%s' does not exist") % old_path);
316 N(! file_exists(new_path),
317 F("rename target file '%s' already exists") % new_path);
318 fs::rename(localized(old_path()),
319 localized(new_path()));
320}
321
322void
323move_dir(file_path const & old_path,
324 file_path const & new_path)
325{
326 N(directory_exists(old_path),
327 F("rename source dir '%s' does not exist") % old_path);
328 N(!directory_exists(new_path),
329 F("rename target dir '%s' already exists") % new_path);
330 fs::rename(localized(old_path()),
331 localized(new_path()));
332}
333
334void
335move_dir(local_path const & old_path,
336 local_path const & new_path)
337{
338 N(directory_exists(old_path),
339 F("rename source dir '%s' does not exist") % old_path);
340 N(!directory_exists(new_path),
341 F("rename target dir '%s' already exists") % new_path);
342 fs::rename(localized(old_path()),
343 localized(new_path()));
344}
345
346void
347mkdir_p(local_path const & p)
348{
349 fs::create_directories(localized(p()));
350}
351
352void
353mkdir_p(file_path const & p)
354{
355 fs::create_directories(localized(p()));
356}
357
358void
359make_dir_for(file_path const & p)
360{
361 fs::path tmp = localized(p());
362 if (tmp.has_branch_path())
363 {
364 fs::create_directories(tmp.branch_path());
365 }
366}
367
368static void
369read_data_impl(fs::path const & p,
370 data & dat)
371{
372 if (!fs::exists(p))
373 throw oops("file '" + p.string() + "' does not exist");
374
375 if (fs::is_directory(p))
376 throw oops("file '" + p.string() + "' cannot be read as data; it is a directory");
377
378 ifstream file(p.string().c_str(),
379 ios_base::in | ios_base::binary);
380 string in;
381 if (!file)
382 throw oops(string("cannot open file ") + p.string() + " for reading");
383 CryptoPP::FileSource f(file, true, new CryptoPP::StringSink(in));
384 dat = in;
385}
386
387// This function can only be called once per run.
388static void
389read_data_stdin(data & dat)
390{
391 static bool have_consumed_stdin = false;
392 N(!have_consumed_stdin, F("Cannot read standard input multiple times"));
393 have_consumed_stdin = true;
394 string in;
395 CryptoPP::FileSource f(cin, true, new CryptoPP::StringSink(in));
396 dat = in;
397}
398
399void
400read_data(local_path const & path, data & dat)
401{
402 read_data_impl(localized(path()), dat);
403}
404
405void
406read_data(file_path const & path, data & dat)
407{
408 read_data_impl(localized(path()), dat);
409}
410
411void
412read_data(local_path const & path,
413 base64< gzip<data> > & dat)
414{
415 data data_plain;
416 read_data_impl(localized(path()), data_plain);
417 gzip<data> data_compressed;
418 base64< gzip<data> > data_encoded;
419 encode_gzip(data_plain, data_compressed);
420 encode_base64(data_compressed, dat);
421}
422
423void
424read_data(file_path const & path,
425 base64< gzip<data> > & dat)
426{
427 read_data(local_path(path()), dat);
428}
429
430void
431read_localized_data(file_path const & path,
432 base64< gzip<data> > & dat,
433 lua_hooks & lua)
434{
435 data data_plain;
436 read_localized_data(path, data_plain, lua);
437 gzip<data> data_compressed;
438 base64< gzip<data> > data_encoded;
439 encode_gzip(data_plain, data_compressed);
440 encode_base64(data_compressed, dat);
441}
442
443void
444read_localized_data(file_path const & path,
445 data & dat,
446 lua_hooks & lua)
447{
448 string db_linesep, ext_linesep;
449 string db_charset, ext_charset;
450
451 bool do_lineconv = (lua.hook_get_linesep_conv(path, db_linesep, ext_linesep)
452 && db_linesep != ext_linesep);
453
454 bool do_charconv = (lua.hook_get_charset_conv(path, db_charset, ext_charset)
455 && db_charset != ext_charset);
456
457 data tdat;
458 read_data(path, tdat);
459
460 string tmp1, tmp2;
461 tmp2 = tdat();
462 if (do_charconv)
463 charset_convert(ext_charset, db_charset, tmp1, tmp2);
464 tmp1 = tmp2;
465 if (do_lineconv)
466 line_end_convert(db_linesep, tmp1, tmp2);
467 dat = tmp2;
468}
469
470
471void
472read_data_for_command_line(utf8 const & path, data & dat)
473{
474 if (path() == "-")
475 read_data_stdin(dat);
476 else
477 read_data_impl(localized(path()), dat);
478}
479
480// FIXME: this is probably not enough brains to actually manage "atomic
481// filesystem writes". at some point you have to draw the line with even
482// trying, and I'm not sure it's really a strict requirement of this tool,
483// but you might want to make this code a bit tighter.
484
485
486static void
487write_data_impl(fs::path const & p,
488 data const & dat)
489{
490 if (fs::exists(p) && fs::is_directory(p))
491 throw oops("file '" + p.string() + "' cannot be over-written as data; it is a directory");
492
493 fs::create_directories(mkpath(p.branch_path().string()));
494
495 // we write, non-atomically, to MT/data.tmp.
496 // nb: no mucking around with multiple-writer conditions. we're a
497 // single-user single-threaded program. you get what you paid for.
498 fs::path mtdir = mkpath(book_keeping_dir);
499 fs::create_directories(mtdir);
500 fs::path tmp = mtdir / "data.tmp";
501
502 {
503 // data.tmp opens
504 ofstream file(tmp.string().c_str(),
505 ios_base::out | ios_base::trunc | ios_base::binary);
506 if (!file)
507 throw oops(string("cannot open file ") + tmp.string() + " for writing");
508 CryptoPP::StringSource s(dat(), true, new CryptoPP::FileSink(file));
509 // data.tmp closes
510 }
511
512 // god forgive my portability sins
513 if (fs::exists(p))
514 N(unlink(p.string().c_str()) == 0,
515 F("unlinking %s failed") % p.string());
516 N(rename(tmp.string().c_str(), p.string().c_str()) == 0,
517 F("rename of %s to %s failed") % tmp.string() % p.string());
518}
519
520void
521write_data(local_path const & path, data const & dat)
522{
523 write_data_impl(localized(path()), dat);
524}
525
526void
527write_data(file_path const & path, data const & dat)
528{
529 write_data_impl(localized(path()), dat);
530}
531
532void
533write_localized_data(file_path const & path,
534 data const & dat,
535 lua_hooks & lua)
536{
537 string db_linesep, ext_linesep;
538 string db_charset, ext_charset;
539
540 bool do_lineconv = (lua.hook_get_linesep_conv(path, db_linesep, ext_linesep)
541 && db_linesep != ext_linesep);
542
543 bool do_charconv = (lua.hook_get_charset_conv(path, db_charset, ext_charset)
544 && db_charset != ext_charset);
545
546 string tmp1, tmp2;
547 tmp2 = dat();
548 if (do_lineconv)
549 line_end_convert(ext_linesep, tmp1, tmp2);
550 tmp1 = tmp2;
551 if (do_charconv)
552 charset_convert(db_charset, ext_charset, tmp1, tmp2);
553
554 write_data(path, data(tmp2));
555}
556
557void
558write_localized_data(file_path const & path,
559 base64< gzip<data> > const & dat,
560 lua_hooks & lua)
561{
562 gzip<data> data_decoded;
563 data data_decompressed;
564 decode_base64(dat, data_decoded);
565 decode_gzip(data_decoded, data_decompressed);
566 write_localized_data(path, data_decompressed, lua);
567}
568
569void
570write_data(local_path const & path,
571 base64< gzip<data> > const & dat)
572{
573 gzip<data> data_decoded;
574 data data_decompressed;
575 decode_base64(dat, data_decoded);
576 decode_gzip(data_decoded, data_decompressed);
577 write_data_impl(localized(path()), data_decompressed);
578}
579
580void
581write_data(file_path const & path,
582 base64< gzip<data> > const & dat)
583{
584 write_data(local_path(path()), dat);
585}
586
587
588tree_walker::~tree_walker() {}
589
590static void
591walk_tree_recursive(fs::path const & absolute,
592 fs::path const & relative,
593 tree_walker & walker)
594{
595 fs::directory_iterator ei;
596 for(fs::directory_iterator di(absolute);
597 di != ei; ++di)
598 {
599 fs::path entry = mkpath(di->string());
600 fs::path rel_entry = relative / mkpath(entry.leaf());
601
602 if (book_keeping_file(rel_entry))
603 {
604 L(F("ignoring book keeping entry %s\n") % rel_entry.string());
605 continue;
606 }
607
608 if (!fs::exists(entry)
609 || di->string() == "."
610 || di->string() == "..")
611 ; // ignore
612 else if (fs::is_directory(entry))
613 walk_tree_recursive(entry, rel_entry, walker);
614 else
615 {
616 file_path p;
617 try
618 {
619 p = file_path(rel_entry.string());
620 }
621 catch (std::runtime_error const & c)
622 {
623 L(F("caught runtime error %s constructing file path for %s\n")
624 % c.what() % rel_entry.string());
625 continue;
626 }
627 walker.visit_file(p);
628 }
629 }
630}
631
632// from some (safe) sub-entry of cwd
633void
634walk_tree(file_path const & path,
635 tree_walker & walker,
636 bool require_existing_path)
637{
638 if (fs::exists(localized(path())))
639 {
640 if (! fs::is_directory(localized(path())))
641 walker.visit_file(path);
642 else
643 {
644 fs::path root(localized(fs::current_path().string()));
645 fs::path rel(localized(path()));
646 walk_tree_recursive(root / rel, rel, walker);
647 }
648 }
649 else
650 {
651 if (require_existing_path)
652 {
653 N(false,
654 F("no such file or directory: %s") % path());
655 }
656 else
657 {
658 walker.visit_file(path);
659 }
660 }
661}
662
663// from cwd (nb: we can't describe cwd as a file_path)
664void
665walk_tree(tree_walker & walker)
666{
667 walk_tree_recursive(fs::current_path(), fs::path(), walker);
668}
669
670
671#ifdef BUILD_UNIT_TESTS
672#include "unit_tests.hh"
673
674static void
675test_book_keeping_file()
676{
677 // positive tests
678
679 BOOST_CHECK(book_keeping_file(local_path("MT")));
680 BOOST_CHECK(book_keeping_file(local_path("MT/foo")));
681 BOOST_CHECK(book_keeping_file(local_path("MT/foo/bar/baz")));
682
683 BOOST_CHECK(book_keeping_file(fs::path("MT")));
684 BOOST_CHECK(book_keeping_file(fs::path("MT/foo")));
685 BOOST_CHECK(book_keeping_file(fs::path("MT/foo/bar/baz")));
686
687 // negative tests
688
689 BOOST_CHECK( ! book_keeping_file(local_path("safe")));
690 BOOST_CHECK( ! book_keeping_file(local_path("safe/path")));
691 BOOST_CHECK( ! book_keeping_file(local_path("safe/path/MT")));
692 BOOST_CHECK( ! book_keeping_file(local_path("MTT")));
693
694 BOOST_CHECK( ! book_keeping_file(fs::path("safe")));
695 BOOST_CHECK( ! book_keeping_file(fs::path("safe/path")));
696 BOOST_CHECK( ! book_keeping_file(fs::path("safe/path/MT")));
697 BOOST_CHECK( ! book_keeping_file(fs::path("MTT")));
698}
699
700void
701add_file_io_tests(test_suite * suite)
702{
703 I(suite);
704 suite->add(BOOST_TEST_CASE(&test_book_keeping_file));
705}
706
707#endif // BUILD_UNIT_TESTS

Archive Download this file

Branches

Tags

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