monotone

monotone Mtn Source Tree

Root/src/paths.cc

1// Copyright (C) 2005 Nathaniel Smith <njs@pobox.com>
2// 2008, 2010 - 2011 Stephen Leake <stephen_leake@stephe-leake.org>
3//
4// This program is made available under the GNU GPL version 2.0 or
5// greater. See the accompanying file COPYING for details.
6//
7// This program is distributed WITHOUT ANY WARRANTY; without even the
8// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9// PURPOSE.
10
11#include "base.hh"
12#include <sstream>
13
14#include "paths.hh"
15#include "file_io.hh"
16#include "charset.hh"
17#include "safe_map.hh"
18
19using std::exception;
20using std::map;
21using std::make_pair;
22using std::make_shared;
23using std::ostream;
24using std::ostringstream;
25using std::string;
26using std::vector;
27
28// some structure to ensure we aren't doing anything broken when resolving
29// filenames. the idea is to make sure
30// -- we don't depend on the existence of something before it has been set
31// -- we don't re-set something that has already been used
32// -- sometimes, we use the _non_-existence of something, so we shouldn't
33// set anything whose un-setted-ness has already been used
34template <typename T>
35struct access_tracker
36{
37 void set(T const & val, bool may_be_initialized)
38 {
39 I(may_be_initialized || !initialized);
40 I(!very_uninitialized);
41 I(!used);
42 initialized = true;
43 value = val;
44 }
45 T const & get()
46 {
47 I(initialized);
48 used = true;
49 return value;
50 }
51 T const & get_but_unused()
52 {
53 I(initialized);
54 return value;
55 }
56 void may_not_initialize()
57 {
58 I(!initialized);
59 very_uninitialized = true;
60 }
61 // for unit tests
62 void unset()
63 {
64 used = initialized = very_uninitialized = false;
65 }
66 T value;
67 bool initialized, used, very_uninitialized;
68 access_tracker() : initialized(false), used(false), very_uninitialized(false) {};
69};
70
71// paths to use in interpreting paths from various sources,
72// conceptually:
73// working_root / initial_rel_path == initial_abs_path
74
75// initial_abs_path is for interpreting relative system_path's
76static access_tracker<system_path> initial_abs_path;
77// initial_rel_path is for interpreting external file_path's
78// we used to make it a file_path, but then you can't run monotone from
79// inside the _MTN/ dir (even when referring to files outside the _MTN/
80// dir). use of a bare string requires some caution but does work.
81static access_tracker<string> initial_rel_path;
82// working_root is for converting file_path's and bookkeeping_path's to
83// system_path's.
84static access_tracker<system_path> working_root;
85
86void
87save_initial_path()
88{
89 // FIXME: BUG: this only works if the current working dir is in utf8
90 initial_abs_path.set(system_path(get_current_working_dir(),
91 origin::system), false);
92 L(FL("initial abs path is: %s") % initial_abs_path.get_but_unused());
93}
94
95///////////////////////////////////////////////////////////////////////////
96// verifying that internal paths are indeed normalized.
97// this code must be superfast
98///////////////////////////////////////////////////////////////////////////
99
100// normalized means:
101// -- / as path separator
102// -- not an absolute path (on either posix or win32)
103// operationally, this means: first character != '/', first character != '\',
104// second character != ':'
105// -- no illegal characters
106// -- 0x00 -- 0x1f, 0x7f, \ are the illegal characters. \ is illegal
107// unconditionally to prevent people checking in files on posix that
108// have a different interpretation on win32
109// -- (may want to allow 0x0a and 0x0d (LF and CR) in the future, but this
110// is blocked on manifest format changing)
111// (also requires changes to 'automate inventory', possibly others, to
112// handle quoting)
113// -- no doubled /'s
114// -- no trailing /
115// -- no "." or ".." path components
116
117static inline bool
118bad_component(string const & component)
119{
120 if (component.empty())
121 return true;
122 if (component == ".")
123 return true;
124 if (component == "..")
125 return true;
126 return false;
127}
128
129static inline bool
130has_bad_chars(string const & path)
131{
132 for (string::const_iterator c = path.begin(); LIKELY(c != path.end()); c++)
133 {
134 // char is often a signed type; convert to unsigned to ensure that
135 // bytes 0x80-0xff are considered > 0x1f.
136 u8 x = (u8)*c;
137 // 0x5c is '\\'; we use the hex constant to make the dependency on
138 // ASCII encoding explicit.
139 if (UNLIKELY(x <= 0x1f || x == 0x5c || x == 0x7f))
140 return true;
141 }
142 return false;
143}
144
145// as above, but disallows / as well.
146static inline bool
147has_bad_component_chars(string const & pc)
148{
149 for (string::const_iterator c = pc.begin(); LIKELY(c != pc.end()); c++)
150 {
151 // char is often a signed type; convert to unsigned to ensure that
152 // bytes 0x80-0xff are considered > 0x1f.
153 u8 x = (u8)*c;
154 // 0x2f is '/' and 0x5c is '\\'; we use hex constants to make the
155 // dependency on ASCII encoding explicit.
156 if (UNLIKELY(x <= 0x1f || x == 0x2f || x == 0x5c || x == 0x7f))
157 return true;
158 }
159 return false;
160
161}
162
163static bool
164is_absolute_here(string const & path)
165{
166 if (path.empty())
167 return false;
168 if (path[0] == '/')
169 return true;
170#if defined(_WIN32) || defined(_WIN64)
171 if (path[0] == '\\')
172 return true;
173 if (path.size() > 1 && path[1] == ':')
174 return true;
175#endif
176 return false;
177}
178
179static inline bool
180is_absolute_somewhere(string const & path)
181{
182 if (path.empty())
183 return false;
184 if (path[0] == '/')
185 return true;
186 if (path[0] == '\\')
187 return true;
188 if (path.size() > 1 && path[1] == ':')
189 return true;
190 return false;
191}
192
193// fully_normalized_path verifies a complete pathname for validity and
194// having been properly normalized (as if by normalize_path, below).
195static inline bool
196fully_normalized_path(string const & path)
197{
198 // empty path is fine
199 if (path.empty())
200 return true;
201 // could use is_absolute_somewhere, but this is the only part of it that
202 // wouldn't be redundant
203 if (path.size() > 1 && path[1] == ':')
204 return false;
205 // first scan for completely illegal bytes
206 if (has_bad_chars(path))
207 return false;
208 // now check each component
209 string::size_type start = 0, stop;
210 while (1)
211 {
212 stop = path.find('/', start);
213 if (stop == string::npos)
214 break;
215 string const & s(path.substr(start, stop - start));
216 if (bad_component(s))
217 return false;
218 start = stop + 1;
219 }
220
221 string const & s(path.substr(start));
222 return !bad_component(s);
223}
224
225// This function considers _MTN, _MTn, _MtN, _mtn etc. to all be bookkeeping
226// paths, because on case insensitive filesystems, files put in any of them
227// may end up in _MTN instead. This allows arbitrary code execution. A
228// better solution would be to fix this in the working directory writing
229// code -- this prevents all-unix projects from naming things "_mtn", which
230// is less rude than when the bookkeeping root was "MT", but still rude --
231// but as a temporary security kluge it works.
232static inline bool
233in_bookkeeping_dir(string const & path)
234{
235 if (path.empty() || (path[0] != '_'))
236 return false;
237 if (path.size() == 1 || (path[1] != 'M' && path[1] != 'm'))
238 return false;
239 if (path.size() == 2 || (path[2] != 'T' && path[2] != 't'))
240 return false;
241 if (path.size() == 3 || (path[3] != 'N' && path[3] != 'n'))
242 return false;
243 // if we've gotten here, the first three letters are _, M, T, and N, in
244 // either upper or lower case. So if that is the whole path, or else if it
245 // continues but the next character is /, then this is a bookkeeping path.
246 if (path.size() == 4 || (path[4] == '/'))
247 return true;
248 return false;
249}
250
251static inline bool
252is_valid_internal(string const & path)
253{
254 return (fully_normalized_path(path)
255 && !in_bookkeeping_dir(path));
256}
257
258static string
259normalize_path(string const & in)
260{
261 string inT = in;
262 string leader;
263 MM(inT);
264
265#if defined(_WIN32) || defined(_WIN64)
266 // the first thing we do is kill all the backslashes
267 for (string::iterator i = inT.begin(); i != inT.end(); i++)
268 if (*i == '\\')
269 *i = '/';
270#endif
271
272 if (is_absolute_here (inT))
273 {
274 if (inT[0] == '/')
275 {
276 leader = "/";
277 inT = inT.substr(1);
278
279 if (!inT.empty() && inT[0] == '/')
280 {
281 // if there are exactly two slashes at the beginning they
282 // are both preserved. three or more are the same as one.
283 string::size_type f = inT.find_first_not_of("/");
284 if (f == string::npos)
285 f = inT.size();
286 if (f == 1)
287 leader = "//";
288 inT = inT.substr(f);
289 }
290 }
291#if defined(_WIN32) || defined(_WIN64)
292 else
293 {
294 I(inT.size() > 1 && inT[1] == ':');
295 if (inT.size() > 2 && inT[2] == '/')
296 {
297 leader = inT.substr(0, 3);
298 inT = inT.substr(3);
299 }
300 else
301 {
302 leader = inT.substr(0, 2);
303 inT = inT.substr(2);
304 }
305 }
306#endif
307
308 I(!is_absolute_here(inT));
309 if (inT.empty())
310 return leader;
311 }
312
313 vector<string> stack;
314 string::const_iterator head, tail;
315 string::size_type size_estimate = leader.size();
316 for (head = inT.begin(); head != inT.end(); head = tail)
317 {
318 tail = head;
319 while (tail != inT.end() && *tail != '/')
320 tail++;
321
322 string elt(head, tail);
323 while (tail != inT.end() && *tail == '/')
324 tail++;
325
326 if (elt == ".")
327 continue;
328 // remove foo/.. element pairs; leave leading .. components alone
329 if (elt == ".." && !stack.empty() && stack.back() != "..")
330 {
331 stack.pop_back();
332 continue;
333 }
334
335 size_estimate += elt.size() + 1;
336 stack.push_back(elt);
337 }
338
339 leader.reserve(size_estimate);
340 for (vector<string>::const_iterator i = stack.begin(); i != stack.end(); i++)
341 {
342 if (i != stack.begin())
343 leader += "/";
344 leader += *i;
345 }
346 return leader;
347}
348
349void
350normalize_external_path(string const & path, string & normalized, bool to_workspace_root)
351{
352 if (!initial_rel_path.initialized)
353 {
354 // we are not in a workspace; treat this as an internal
355 // path, and set the access_tracker() into a very uninitialised
356 // state so that we will hit an exception if we do eventually
357 // enter a workspace
358 initial_rel_path.may_not_initialize();
359 normalized = path;
360 E(is_valid_internal(path), origin::user,
361 F("path '%s' is invalid") % path);
362 }
363 else
364 {
365 E(!is_absolute_here(path), origin::user,
366 F("absolute path '%s' is invalid") % path);
367 string base;
368 try
369 {
370 if (to_workspace_root)
371 base = "";
372 else
373 base = initial_rel_path.get();
374
375 if (base == "")
376 normalized = normalize_path(path);
377 else
378 normalized = normalize_path(base + "/" + path);
379 }
380 catch (exception &)
381 {
382 E(false, origin::user, F("path '%s' is invalid") % path);
383 }
384 if (normalized == ".")
385 normalized = string("");
386 E(fully_normalized_path(normalized), origin::user,
387 F("path '%s' is invalid") % normalized);
388 }
389}
390
391///////////////////////////////////////////////////////////////////////////
392// single path component handling.
393///////////////////////////////////////////////////////////////////////////
394
395// these constructors confirm that what they are passed is a legitimate
396// component. note that the empty string is a legitimate component,
397// but is not acceptable to bad_component (above) and therefore we have
398// to open-code most of those checks.
399path_component::path_component(utf8 const & d)
400 : origin_aware(d.made_from), data(d())
401{
402 MM(data);
403 I(!has_bad_component_chars(data) && data != "." && data != "..");
404}
405
406path_component::path_component(string const & d, origin::type whence)
407 : origin_aware(whence), data(d)
408{
409 MM(data);
410 I(utf8_validate(utf8(data, origin::internal))
411 && !has_bad_component_chars(data)
412 && data != "." && data != "..");
413}
414
415path_component::path_component(char const * d)
416 : data(d)
417{
418 MM(data);
419 I(utf8_validate(utf8(data, origin::internal))
420 && !has_bad_component_chars(data)
421 && data != "." && data != "..");
422}
423
424std::ostream & operator<<(std::ostream & s, path_component const & pc)
425{
426 return s << pc();
427}
428
429template <> void dump(path_component const & pc, std::string & to)
430{
431 to = pc();
432}
433
434///////////////////////////////////////////////////////////////////////////
435// complete paths to files within a working directory
436///////////////////////////////////////////////////////////////////////////
437
438file_path::file_path(file_path::source_type type, string const & path, bool to_workspace_root)
439{
440 MM(path);
441 I(utf8_validate(utf8(path, origin::internal)));
442 if (type == external)
443 {
444 string normalized;
445 normalize_external_path(path, normalized, to_workspace_root);
446 E(!in_bookkeeping_dir(normalized), origin::user,
447 F("path '%s' is in bookkeeping dir") % normalized);
448 data = normalized;
449 }
450 else
451 data = path;
452 MM(data);
453 I(is_valid_internal(data));
454}
455
456file_path::file_path(file_path::source_type type, utf8 const & path,
457 bool to_workspace_root)
458 : any_path(path.made_from)
459{
460 MM(path);
461 E(utf8_validate(path), made_from, F("invalid utf8"));
462 if (type == external)
463 {
464 string normalized;
465 normalize_external_path(path(), normalized, to_workspace_root);
466 E(!in_bookkeeping_dir(normalized), origin::user,
467 F("path '%s' is in bookkeeping dir") % normalized);
468 data = normalized;
469 }
470 else
471 data = path();
472 MM(data);
473 I(is_valid_internal(data));
474}
475
476bookkeeping_path::bookkeeping_path(char const * const path)
477{
478 I(fully_normalized_path(path));
479 I(in_bookkeeping_dir(path));
480 data = path;
481}
482
483bookkeeping_path::bookkeeping_path(string const & path, origin::type made_from)
484{
485 E(fully_normalized_path(path), made_from, F("path is not normalized"));
486 E(in_bookkeeping_dir(path), made_from,
487 F("bookkeeping path is not in bookkeeping directory"));
488 data = path;
489}
490
491bool
492bookkeeping_path::external_string_is_bookkeeping_path(utf8 const & path)
493{
494 // FIXME: this charset casting everywhere is ridiculous
495 string normalized;
496 try
497 {
498 normalize_external_path(path(), normalized, false);
499 }
500 catch (recoverable_failure &)
501 {
502 return false;
503 }
504 return internal_string_is_bookkeeping_path(utf8(normalized, path.made_from));
505}
506bool bookkeeping_path::internal_string_is_bookkeeping_path(utf8 const & path)
507{
508 return in_bookkeeping_dir(path());
509}
510
511///////////////////////////////////////////////////////////////////////////
512// splitting/joining
513// this code must be superfast
514// it depends very much on knowing that it can only be applied to fully
515// normalized, relative, paths.
516///////////////////////////////////////////////////////////////////////////
517
518// this peels off the last component of any path and returns it.
519// the last component of a path with no slashes in it is the complete path.
520// the last component of a path referring to the root directory is an
521// empty string.
522path_component
523any_path::basename() const
524{
525 string const & s = data;
526 string::size_type sep = s.rfind('/');
527#if defined(_WIN32) || defined(_WIN64)
528 if (sep == string::npos && s.size()>= 2 && s[1] == ':')
529 sep = 1;
530#endif
531 if (sep == string::npos)
532 return path_component(s, 0); // force use of short circuit
533 if (sep == s.size())
534 return path_component();
535 return path_component(s, sep + 1);
536}
537
538// this returns all but the last component of any path. It has to take
539// care at the root.
540any_path
541any_path::dirname() const
542{
543 string const & s = data;
544 string::size_type sep = s.rfind('/');
545#if defined(_WIN32) || defined(_WIN64)
546 if (sep == string::npos && s.size()>= 2 && s[1] == ':')
547 sep = 1;
548#endif
549 if (sep == string::npos)
550 return any_path();
551
552 // dirname() of the root directory is itself
553 if (sep == s.size() - 1)
554 return *this;
555
556 // dirname() of a direct child of the root is the root
557 if (sep == 0 || (sep == 1 && s[1] == '/')
558#if defined(_WIN32) || defined(_WIN64)
559 || (sep == 1 || (sep == 2 && s[1] == ':'))
560#endif
561 )
562 return any_path(s, 0, sep+1);
563
564 return any_path(s, 0, sep);
565}
566
567// these variations exist to get the return type right. also,
568// file_path dirname() can be a little simpler.
569file_path
570file_path::dirname() const
571{
572 string const & s = data;
573 string::size_type sep = s.rfind('/');
574 if (sep == string::npos)
575 return file_path();
576 return file_path(s, 0, sep);
577}
578
579system_path
580system_path::dirname() const
581{
582 string const & s = data;
583 string::size_type sep = s.rfind('/');
584#if defined(_WIN32) || defined(_WIN64)
585 if (sep == string::npos && s.size()>= 2 && s[1] == ':')
586 sep = 1;
587#endif
588 I(sep != string::npos);
589
590 // dirname() of the root directory is itself
591 if (sep == s.size() - 1)
592 return *this;
593
594 // dirname() of a direct child of the root is the root
595 if (sep == 0 || (sep == 1 && s[1] == '/')
596#if defined(_WIN32) || defined(_WIN64)
597 || (sep == 1 || (sep == 2 && s[1] == ':'))
598#endif
599 )
600 return system_path(s, 0, sep+1);
601
602 return system_path(s, 0, sep);
603}
604
605
606// produce dirname and basename at the same time
607void
608file_path::dirname_basename(file_path & dir, path_component & base) const
609{
610 string const & s = data;
611 string::size_type sep = s.rfind('/');
612 if (sep == string::npos)
613 {
614 dir = file_path();
615 base = path_component(s, 0);
616 }
617 else
618 {
619 I(sep < s.size() - 1); // last component must have at least one char
620 dir = file_path(s, 0, sep);
621 base = path_component(s, sep + 1);
622 }
623}
624
625// returns true if this path is beneath other
626bool
627file_path::is_beneath_of(const file_path & other) const
628{
629 if (other.empty())
630 return true;
631
632 file_path basedir = dirname();
633 while (!basedir.empty())
634 {
635 L(FL("base: %s, other: %s") % basedir % other);
636 if (basedir == other)
637 return true;
638 basedir = basedir.dirname();
639 }
640 return false;
641}
642
643// count the number of /-separated components of the path.
644unsigned int
645file_path::depth() const
646{
647 if (data.empty())
648 return 0;
649
650 unsigned int components = 1;
651 for (string::const_iterator p = data.begin(); p != data.end(); p++)
652 if (*p == '/')
653 components++;
654
655 return components;
656}
657
658///////////////////////////////////////////////////////////////////////////
659// localizing file names (externalizing them)
660// this code must be superfast when there is no conversion needed
661///////////////////////////////////////////////////////////////////////////
662
663string
664any_path::as_external() const
665{
666#ifdef __APPLE__
667 // on OS X paths for the filesystem/kernel are UTF-8 encoded, regardless of
668 // locale.
669 return data;
670#else
671 // on normal systems we actually have some work to do, alas.
672 // not much, though, because utf8_to_system_string does all the hard work.
673 // it is carefully optimized. do not screw it up.
674 external out;
675 utf8_to_system_strict(utf8(data, made_from), out);
676 return out();
677#endif
678}
679
680///////////////////////////////////////////////////////////////////////////
681// writing out paths
682///////////////////////////////////////////////////////////////////////////
683
684ostream &
685operator <<(ostream & o, any_path const & a)
686{
687 o << a.as_internal();
688 return o;
689}
690
691template <>
692void dump(file_path const & p, string & out)
693{
694 ostringstream oss;
695 oss << p << '\n';
696 out = oss.str();
697}
698
699template <>
700void dump(system_path const & p, string & out)
701{
702 ostringstream oss;
703 oss << p << '\n';
704 out = oss.str();
705}
706
707template <>
708void dump(bookkeeping_path const & p, string & out)
709{
710 ostringstream oss;
711 oss << p << '\n';
712 out = oss.str();
713}
714
715///////////////////////////////////////////////////////////////////////////
716// path manipulation
717// this code's speed does not matter much
718///////////////////////////////////////////////////////////////////////////
719
720// relies on its arguments already being validated, except that you may not
721// append the empty path component, and if you are appending to the empty
722// path, you may not create an absolute path or a path into the bookkeeping
723// directory.
724file_path
725file_path::operator /(path_component const & to_append) const
726{
727 I(!to_append.empty());
728 if (empty())
729 {
730 string const & s = to_append();
731 I(!is_absolute_somewhere(s) && !in_bookkeeping_dir(s));
732 return file_path(s, 0, string::npos);
733 }
734 else
735 return file_path(((*(data.end() - 1) == '/') ? data : data + "/")
736 + to_append(), 0, string::npos);
737}
738
739// similarly, but even less checking is needed.
740file_path
741file_path::operator /(file_path const & to_append) const
742{
743 I(!to_append.empty());
744 if (empty())
745 return to_append;
746 return file_path(((*(data.end() - 1) == '/') ? data : data + "/")
747 + to_append.as_internal(), 0, string::npos);
748}
749
750bookkeeping_path
751bookkeeping_path::operator /(path_component const & to_append) const
752{
753 I(!to_append.empty());
754 I(!empty());
755 return bookkeeping_path(((*(data.end() - 1) == '/') ? data : data + "/")
756 + to_append(), 0, string::npos);
757}
758
759bookkeeping_path
760bookkeeping_path::operator /(file_path const & to_append) const
761{
762 I(!to_append.empty());
763 I(!empty());
764 return bookkeeping_path(((*(data.end() - 1) == '/') ? data : data + "/")
765 + to_append.as_internal(), 0, string::npos);
766}
767
768system_path
769system_path::operator /(path_component const & to_append) const
770{
771 I(!to_append.empty());
772 I(!empty());
773 return system_path(((*(data.end() - 1) == '/') ? data : data + "/")
774 + to_append(), 0, string::npos);
775}
776
777any_path
778any_path::operator /(path_component const & to_append) const
779{
780 I(!to_append.empty());
781 I(!empty());
782 return any_path(((*(data.end() - 1) == '/') ? data : data + "/")
783 + to_append(), 0, string::npos);
784}
785
786// these take strings and validate
787bookkeeping_path
788bookkeeping_path::operator /(char const * to_append) const
789{
790 I(!is_absolute_somewhere(to_append));
791 I(!empty());
792 return bookkeeping_path(((*(data.end() - 1) == '/') ? data : data + "/")
793 + to_append, origin::internal);
794}
795
796system_path
797system_path::operator /(char const * to_append) const
798{
799 I(!empty());
800 I(!is_absolute_here(to_append));
801 return system_path(((*(data.end() - 1) == '/') ? data : data + "/")
802 + to_append, origin::internal);
803}
804
805///////////////////////////////////////////////////////////////////////////
806// system_path
807///////////////////////////////////////////////////////////////////////////
808
809system_path::system_path(any_path const & other, bool in_true_workspace)
810{
811 if (is_absolute_here(other.as_internal()))
812 // another system_path. the normalizing isn't really necessary, but it
813 // makes me feel warm and fuzzy.
814 data = normalize_path(other.as_internal());
815 else
816 {
817 system_path wr;
818 if (in_true_workspace)
819 wr = working_root.get();
820 else
821 wr = working_root.get_but_unused();
822 data = normalize_path(wr.as_internal() + "/" + other.as_internal());
823 }
824}
825
826static inline string const_system_path(utf8 const & path)
827{
828 E(!path().empty(), path.made_from, F("invalid path ''"));
829 string expanded = tilde_expand(path());
830 if (is_absolute_here(expanded))
831 return normalize_path(expanded);
832 else
833 return normalize_path(initial_abs_path.get().as_internal()
834 + "/" + path());
835}
836
837system_path::system_path(string const & path, origin::type from)
838{
839 data = const_system_path(utf8(path, from));
840}
841
842system_path::system_path(char const * const path)
843{
844 data = const_system_path(utf8(path, origin::internal));
845}
846
847system_path::system_path(utf8 const & path)
848{
849 data = const_system_path(utf8(path));
850}
851
852// Constant path predicates.
853#define IMPLEMENT_CONST_PRED(cls, ret) \
854 template <> bool \
855 path_always_##ret<cls>::operator()(cls const &) const \
856 { return ret; }
857
858IMPLEMENT_CONST_PRED(any_path, false)
859IMPLEMENT_CONST_PRED(system_path, false)
860IMPLEMENT_CONST_PRED(file_path, false)
861IMPLEMENT_CONST_PRED(bookkeeping_path, false)
862
863IMPLEMENT_CONST_PRED(any_path, true)
864IMPLEMENT_CONST_PRED(system_path, true)
865IMPLEMENT_CONST_PRED(file_path, true)
866IMPLEMENT_CONST_PRED(bookkeeping_path, true)
867
868#undef IMPLEMENT_CONST_PRED
869
870// If this wasn't a user-supplied path, we should know
871// which kind it is.
872std::shared_ptr<any_path>
873new_optimal_path(std::string path, bool to_workspace_root)
874{
875 utf8 const utf8_path = utf8(path, origin::user);
876 string normalized;
877 try
878 {
879 normalize_external_path(utf8_path(), normalized, to_workspace_root);
880 }
881 catch (recoverable_failure &)
882 {
883 // not in workspace
884 return std::shared_ptr<any_path>(make_shared<system_path>
885 (path, origin::user));
886 }
887
888 if (in_bookkeeping_dir(normalized))
889 return std::shared_ptr<any_path>(make_shared<bookkeeping_path>
890 (normalized, origin::user));
891 else
892 return std::shared_ptr<any_path>(make_shared<file_path>
893 (file_path_internal(normalized)));
894};
895
896// Either conversion of S to a path_component, or composition of P / S, has
897// failed; figure out what went wrong and issue an appropriate diagnostic.
898
899void
900report_failed_path_composition(any_path const & p, char const * s,
901 bool isdir)
902{
903 utf8 badpth;
904 if (p.empty())
905 badpth = utf8(s);
906 else
907 badpth = utf8(p.as_internal() + "/" + s, p.made_from);
908 if (bookkeeping_path::internal_string_is_bookkeeping_path(badpth))
909 L(FL("ignoring bookkeeping directory '%s'") % badpth);
910 else
911 {
912 // We rely on caller to tell us whether this is a directory.
913 if (isdir)
914 W(F("skipping directory '%s' with unsupported name") % badpth);
915 else
916 W(F("skipping file '%s' with unsupported name") % badpth);
917 }
918}
919
920///////////////////////////////////////////////////////////////////////////
921// workspace (and path root) handling
922///////////////////////////////////////////////////////////////////////////
923
924static bool
925find_bookdir(system_path const & root, path_component const & bookdir,
926 system_path & current, string & removed)
927{
928 current = initial_abs_path.get();
929 removed.clear();
930
931 // check that the current directory is below the specified search root
932 if (current.as_internal().find(root.as_internal()) != 0)
933 {
934 W(F("current directory '%s' is not below root '%s'") % current % root);
935 return false;
936 }
937
938 L(FL("searching for '%s' directory with root '%s'") % bookdir % root);
939
940 system_path check;
941 while (!(current == root))
942 {
943 check = current / bookdir;
944 switch (get_path_status(check))
945 {
946 case path::nonexistent:
947 L(FL("'%s' not found in '%s' with '%s' removed")
948 % bookdir % current % removed);
949 if (removed.empty())
950 removed = current.basename()();
951 else
952 removed = current.basename()() + "/" + removed;
953 current = current.dirname();
954 continue;
955
956 case path::file:
957 case path::special:
958 L(FL("'%s' is not a directory") % check);
959 return false;
960
961 case path::directory:
962 goto found;
963 }
964 }
965
966 // if we get here, we have hit the root; try once more
967 check = current / bookdir;
968 switch (get_path_status(check))
969 {
970 case path::nonexistent:
971 L(FL("'%s' not found in '%s' with '%s' removed")
972 % bookdir % current % removed);
973 return false;
974
975 case path::file:
976 case path::special:
977 L(FL("'%s' is not a directory") % check);
978 return false;
979
980 case path::directory:
981 goto found;
982 }
983 return false;
984
985 found:
986 // check for _MTN/. and _MTN/.. to see if mt dir is readable
987 try
988 {
989 if (!directory_exists(check / ".") || !directory_exists(check / ".."))
990 {
991 L(FL("problems with '%s' (missing '.' or '..')") % check);
992 return false;
993 }
994 }
995 catch(exception &)
996 {
997 L(FL("problems with '%s' (cannot check for '.' or '..')") % check);
998 return false;
999 }
1000 return true;
1001}
1002
1003
1004bool
1005find_and_go_to_workspace(string const & search_root)
1006{
1007 system_path root, current;
1008 string removed;
1009
1010 if (search_root.empty())
1011 {
1012#if defined(_WIN32) || defined(_WIN64)
1013 std::string cur_str = get_current_working_dir();
1014 current = system_path(cur_str, origin::system);
1015 if (cur_str[0] == '/' || cur_str[0] == '\\')
1016 {
1017 if (cur_str.size() > 1 && (cur_str[1] == '/' || cur_str[1] == '\\'))
1018 {
1019 // UNC name
1020 string::size_type uncend = cur_str.find_first_of("\\/", 2);
1021 if (uncend == string::npos)
1022 root = system_path(cur_str + "/", origin::system);
1023 else
1024 root = system_path(cur_str.substr(0, uncend), origin::system);
1025 }
1026 else
1027 root = system_path("/");
1028 }
1029 else if (cur_str.size() > 1 && cur_str[1] == ':')
1030 {
1031 root = system_path(cur_str.substr(0,2) + "/", origin::system);
1032 }
1033 else I(false);
1034#else
1035 root = system_path("/", origin::internal);
1036#endif
1037 }
1038 else
1039 {
1040 root = system_path(search_root, origin::user);
1041 L(FL("limiting search for workspace to %s") % root);
1042
1043 require_path_is_directory(root,
1044 F("search root '%s' does not exist") % root,
1045 F("search root '%s' is not a directory") % root,
1046 F("search root '%s' is not a directory") % root);
1047 }
1048
1049 // first look for the current name of the bookkeeping directory.
1050 // if we don't find it, look for it under the old name, so that
1051 // migration has a chance to work.
1052 if (!find_bookdir(root, bookkeeping_root_component, current, removed))
1053 if (!find_bookdir(root, old_bookkeeping_root_component, current, removed))
1054 return false;
1055
1056 working_root.set(current, true);
1057 initial_rel_path.set(removed, true);
1058
1059 L(FL("working root is '%s'") % working_root.get_but_unused());
1060 L(FL("initial relative path is '%s'") % initial_rel_path.get_but_unused());
1061
1062 change_current_working_dir(working_root.get_but_unused());
1063
1064 return true;
1065}
1066
1067void
1068go_to_workspace(system_path const & new_workspace)
1069{
1070 working_root.set(new_workspace, true);
1071 initial_rel_path.set(string(), true);
1072 change_current_working_dir(new_workspace);
1073}
1074
1075void
1076get_current_workspace(system_path & workspace)
1077{
1078 workspace = working_root.get_but_unused();
1079}
1080
1081void
1082mark_std_paths_used(void)
1083{
1084 working_root.get();
1085 initial_rel_path.get();
1086}
1087
1088void
1089reset_std_paths(void)
1090{
1091 // we don't reset initial_abs_path here, because it is only set in
1092 // monotone.cc:cpp_main. initial_rel_path, working_root are reset for each
1093 // command.
1094 initial_rel_path.unset();
1095 working_root.unset();
1096}
1097
1098
1099///////////////////////////////////////////////////////////////////////////
1100// utility used by migrate_ancestry
1101///////////////////////////////////////////////////////////////////////////
1102
1103
1104static file_path
1105find_old_path_for(map<file_path, file_path> const & renames,
1106 file_path const & new_path)
1107{
1108 map<file_path, file_path>::const_iterator i = renames.find(new_path);
1109 if (i != renames.end())
1110 return i->second;
1111
1112 // ??? root directory rename possible in the old schema?
1113 // if not, do this first.
1114 if (new_path.empty())
1115 return new_path;
1116
1117 file_path dir;
1118 path_component base;
1119 new_path.dirname_basename(dir, base);
1120 return find_old_path_for(renames, dir) / base;
1121}
1122
1123file_path
1124find_new_path_for(map<file_path, file_path> const & renames,
1125 file_path const & old_path)
1126{
1127 map<file_path, file_path> reversed;
1128 for (map<file_path, file_path>::const_iterator i = renames.begin();
1129 i != renames.end(); ++i)
1130 reversed.insert(make_pair(i->second, i->first));
1131 // this is a hackish kluge. seems to work, though.
1132 return find_old_path_for(reversed, old_path);
1133}
1134
1135// Local Variables:
1136// mode: C++
1137// fill-column: 76
1138// c-file-style: "gnu"
1139// indent-tabs-mode: nil
1140// End:
1141// 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