File: file_io.cc

package info (click to toggle)
monotone 0.48-3
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 20,096 kB
  • ctags: 8,077
  • sloc: cpp: 81,000; sh: 6,402; perl: 1,241; lisp: 1,045; makefile: 655; python: 566; sql: 112; ansic: 52
file content (485 lines) | stat: -rw-r--r-- 13,411 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
//
// This program is made available under the GNU GPL version 2.0 or
// greater. See the accompanying file COPYING for details.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE.

#include "base.hh"
#include <iostream>
#include <fstream>

#include <botan/botan.h>
#include "botan_pipe_cache.hh"

#include "file_io.hh"
#include "sanity.hh"
#include "simplestring_xform.hh"
#include "charset.hh"
#include "platform-wrapped.hh"
#include "numeric_vocab.hh"
#include "vocab_cast.hh"

// this file deals with talking to the filesystem, loading and
// saving files.

using std::cin;
using std::ifstream;
using std::ios_base;
using std::logic_error;
using std::string;
using std::vector;

void
assert_path_is_nonexistent(any_path const & path)
{
  I(get_path_status(path) == path::nonexistent);
}

void
assert_path_is_file(any_path const & path)
{
  I(get_path_status(path) == path::file);
}

void
assert_path_is_directory(any_path const & path)
{
  I(get_path_status(path) == path::directory);
}

void
require_path_is_nonexistent(any_path const & path,
                            i18n_format const & message)
{
  E(!path_exists(path), origin::user, message);
}

void
require_path_is_file(any_path const & path,
                     i18n_format const & message_if_nonexistent,
                     i18n_format const & message_if_directory)
{
  switch (get_path_status(path))
    {
    case path::nonexistent:
      E(false, origin::user, message_if_nonexistent);
      break;
    case path::file:
      return;
    case path::directory:
      E(false, origin::user, message_if_directory);
      break;
    }
}

void
require_path_is_directory(any_path const & path,
                          i18n_format const & message_if_nonexistent,
                          i18n_format const & message_if_file)
{
  switch (get_path_status(path))
    {
    case path::nonexistent:
      E(false, origin::user, message_if_nonexistent);
      break;
    case path::file:
      E(false, origin::user, message_if_file);
    case path::directory:
      return;
      break;
    }
}

bool
path_exists(any_path const & p)
{
  return get_path_status(p) != path::nonexistent;
}

bool
directory_exists(any_path const & p)
{
  return get_path_status(p) == path::directory;
}

bool
file_exists(any_path const & p)
{
  return get_path_status(p) == path::file;
}

bool
directory_empty(any_path const & path)
{
  struct directory_not_empty_exception {};
  struct directory_empty_helper : public dirent_consumer
  {
    virtual void consume(char const *)
    { throw directory_not_empty_exception(); }
  };

  directory_empty_helper h;
  try {
    read_directory(path, h, h, h);
  } catch (directory_not_empty_exception) {
    return false;
  }
  return true;
}

// This is not the greatest heuristic ever; it just looks for characters in
// the original ASCII control code range (00 - 1f, 7f) that are not white
// space (07 - 0D).  But then, GNU diff just looks for NULs.  We could do
// better if this was detecting character encoding (because then we could
// report wide encodings as such instead of treating them as binary) but the
// application proper isn't set up for that.
//
// Everything > 128 *can* be a valid text character, depending on encoding,
// even in the 80 - 9F region that Unicode reserves for yet more useless
// control characters.
//
// N.B. the obvious algorithmic version of the inner loop here
//     u8 c = s[i];
//     if (c <= 0x06 || (c >= 0x0E && c < 0x20) || c == 0x7F)
//       return true;
// is about twice as slow on current hardware (Intel Core2 quad).

bool guess_binary(string const & s)
{
  static const bool char_is_binary[256] = {
  //_0 _1 _2 _3 _4 _5 _6 _7 _8 _9 _A _B _C _D _E _F
    1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, // 0_
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1_
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2_
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3_
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4_
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5_
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6_
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 7_
    0                                               // 8+
  };

  for (size_t i = 0; i < s.size(); ++i)
    {
      if (char_is_binary[ static_cast<u8>(s[i]) ])
        return true;
    }
  return false;
}

void
mkdir_p(any_path const & p)
{
  switch (get_path_status(p))
    {
    case path::directory:
      return;
    case path::file:
      E(false, origin::system,
        F("could not create directory '%s': it is a file") % p);
    case path::nonexistent:
      std::string const current = p.as_external();
      any_path const parent = p.dirname();
      if (current != parent.as_external())
        {
          mkdir_p(parent);
        }
      do_mkdir(current);
    }
}

void
make_dir_for(any_path const & p)
{
  mkdir_p(p.dirname());
}

void
delete_file(any_path const & p)
{
  require_path_is_file(p,
                       F("file to delete '%s' does not exist") % p,
                       F("file to delete, '%s', is not a file but a directory") % p);
  do_remove(p.as_external());
}

void
delete_dir_shallow(any_path const & p)
{
  require_path_is_directory(p,
                            F("directory to delete '%s' does not exist") % p,
                            F("directory to delete, '%s', is not a directory but a file") % p);
  do_remove(p.as_external());
}

void
delete_file_or_dir_shallow(any_path const & p)
{
  E(path_exists(p), origin::user,
    F("object to delete, '%s', does not exist") % p);
  do_remove(p.as_external());
}

void
delete_dir_recursive(any_path const & p)
{
  require_path_is_directory(p,
                            F("directory to delete, '%s', does not exist") % p,
                            F("directory to delete, '%s', is a file") % p);

  do_remove_recursive(p.as_external());
}

void
move_file(any_path const & old_path,
          any_path const & new_path)
{
  require_path_is_file(old_path,
                       F("rename source file '%s' does not exist") % old_path,
                       F("rename source file '%s' is a directory "
                         "-- bug in monotone?") % old_path);
  require_path_is_nonexistent(new_path,
                              F("rename target '%s' already exists")
                              % new_path);
  rename_clobberingly(old_path.as_external(), new_path.as_external());
}

void
move_dir(any_path const & old_path,
         any_path const & new_path)
{
  require_path_is_directory(old_path,
                            F("rename source dir '%s' does not exist")
                            % old_path,
                            F("rename source dir '%s' is a file "
                              "-- bug in monotone?") % old_path);
  require_path_is_nonexistent(new_path,
                              F("rename target '%s' already exists")
                              % new_path);
  rename_clobberingly(old_path.as_external(), new_path.as_external());
}

void
move_path(any_path const & old_path,
          any_path const & new_path)
{
  E(path_exists(old_path), origin::user,
    F("rename source path '%s' does not exist") % old_path);
  require_path_is_nonexistent(new_path,
                              F("rename target '%s' already exists")
                              % new_path);
  rename_clobberingly(old_path.as_external(), new_path.as_external());
}

void
read_data(any_path const & p, data & dat)
{
  require_path_is_file(p,
                       F("file %s does not exist") % p,
                       F("file %s cannot be read as data; it is a directory") % p);

  ifstream file(p.as_external().c_str(),
                ios_base::in | ios_base::binary);
  E(file, origin::user, F("cannot open file %s for reading") % p);
  unfiltered_pipe->start_msg();
  file >> *unfiltered_pipe;
  unfiltered_pipe->end_msg();
  origin::type data_from = p.made_from;
  if (data_from != origin::internal || data_from == origin::database)
    data_from = origin::system;
  dat = data(unfiltered_pipe->read_all_as_string(Botan::Pipe::LAST_MESSAGE),
             data_from);
}

// This function can only be called once per run.
void
read_data_stdin(data & dat)
{
  static bool have_consumed_stdin = false;
  E(!have_consumed_stdin, origin::user,
    F("Cannot read standard input multiple times"));
  have_consumed_stdin = true;
  unfiltered_pipe->start_msg();
  cin >> *unfiltered_pipe;
  unfiltered_pipe->end_msg();
  dat = data(unfiltered_pipe->read_all_as_string(Botan::Pipe::LAST_MESSAGE),
             origin::user);
}

void
read_data_for_command_line(utf8 const & path, data & dat)
{
  if (path() == "-")
    read_data_stdin(dat);
  else
    read_data(system_path(path), dat);
}


// FIXME: this is probably not enough brains to actually manage "atomic
// filesystem writes". at some point you have to draw the line with even
// trying, and I'm not sure it's really a strict requirement of this tool,
// but you might want to make this code a bit tighter.

static void
write_data_impl(any_path const & p,
                data const & dat,
                any_path const & tmp,
                bool user_private)
{
  E(!directory_exists(p), origin::user,
    F("file '%s' cannot be overwritten as data; it is a directory") % p);

  make_dir_for(p);

  write_data_worker(p.as_external(), dat(), tmp.as_external(), user_private);
}

void
write_data(file_path const & path, data const & dat)
{
  // use the bookkeeping root as the temporary directory.
  assert_path_is_directory(bookkeeping_root);
  write_data_impl(path, dat, bookkeeping_root, false);
}

void
write_data(bookkeeping_path const & path, data const & dat)
{
  // use the bookkeeping root as the temporary directory.
  assert_path_is_directory(bookkeeping_root);
  write_data_impl(path, dat, bookkeeping_root, false);
}

void
write_data(system_path const & path,
           data const & data,
           system_path const & tmpdir)
{
  write_data_impl(path, data, tmpdir, false);
}

void
write_data_userprivate(system_path const & path,
                       data const & data,
                       system_path const & tmpdir)
{
  write_data_impl(path, data, tmpdir, true);
}

// recursive directory walking

tree_walker::~tree_walker() {}

bool
tree_walker::visit_dir(file_path const & path)
{
  return true;
}

static void
walk_tree_recursive(file_path const & path,
                    tree_walker & walker)
{
  // Read the directory up front, so that the directory handle is released
  // before we recurse.  This is important, because it can allocate rather a
  // bit of memory (especially on ReiserFS, see [1]; opendir uses the
  // filesystem's blocksize as a clue how much memory to allocate).  We used
  // to recurse into subdirectories on the fly; this left the memory
  // describing _this_ directory pinned on the heap.  Then our recursive
  // call itself made another recursive call, etc., causing a huge spike in
  // peak memory.  By splitting the loop in half, we avoid this problem.
  //
  // [1] http://lkml.org/lkml/2006/2/24/215
  vector<file_path> files, dirs;
  fill_path_vec<file_path> fill_files(path, files, false);
  fill_path_vec<file_path> fill_dirs(path, dirs, true);

  read_directory(path, fill_files, fill_dirs);

  for (vector<file_path>::const_iterator i = files.begin();
       i != files.end(); ++i)
    walker.visit_file(*i);

  for (vector<file_path>::const_iterator i = dirs.begin();
       i != dirs.end(); ++i)
    if (walker.visit_dir(*i))
      walk_tree_recursive(*i, walker);
}

// from some (safe) sub-entry of cwd
void
walk_tree(file_path const & path, tree_walker & walker)
{
  if (path.empty())
    {
      walk_tree_recursive(path, walker);
      return;
    }

  switch (get_path_status(path))
    {
    case path::nonexistent:
      E(false, origin::user, F("no such file or directory: '%s'") % path);
      break;
    case path::file:
      walker.visit_file(path);
      break;
    case path::directory:
      if (walker.visit_dir(path))
        walk_tree_recursive(path, walker);
      break;
    }
}

bool
ident_existing_file(file_path const & p, file_id & ident)
{
  return ident_existing_file(p, ident, get_path_status(p));
}

bool
ident_existing_file(file_path const & p, file_id & ident, path::status status)
{
  switch (status)
    {
    case path::nonexistent:
      return false;
    case path::file:
      break;
    case path::directory:
      W(F("expected file '%s', but it is a directory.") % p);
      return false;
    }

  calculate_ident(p, ident);
  return true;
}

void
calculate_ident(file_path const & file,
                file_id & ident)
{
  // no conversions necessary, use streaming form
  static cached_botan_pipe
    p(new Botan::Pipe(new Botan::Hash_Filter("SHA-160")));

  // Best to be safe and check it isn't a dir.
  assert_path_is_file(file);
  Botan::DataSource_Stream infile(file.as_external(), true);
  p->process_msg(infile);

  ident = file_id(p->read_all_as_string(Botan::Pipe::LAST_MESSAGE),
                  origin::internal);
}

// Local Variables:
// mode: C++
// fill-column: 76
// c-file-style: "gnu"
// indent-tabs-mode: nil
// End:
// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: