monotone

monotone Mtn Source Tree

Root/unix/fs.cc

1// copyright (C) 2005 nathaniel smith <njs@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
7#ifndef _FILE_OFFSET_BITS
8#define _FILE_OFFSET_BITS 64
9#endif
10
11#include "base.hh"
12#include <unistd.h>
13#include <errno.h>
14#include <stdlib.h>
15#include <string.h>
16#include <sys/types.h>
17#include <sys/stat.h>
18#include <sys/time.h>
19#include <pwd.h>
20#include <stdio.h>
21#include <fcntl.h>
22#include <dirent.h>
23
24#include "sanity.hh"
25#include "platform.hh"
26
27using std::string;
28
29/* On Linux, AT_SYMLNK_NOFOLLOW is spellt AT_SYMLINK_NOFOLLOW.
30 Hoooray for compatibility! */
31#if defined AT_SYMLINK_NOFOLLOW && !defined AT_SYMLNK_NOFOLLOW
32#define AT_SYMLNK_NOFOLLOW AT_SYMLINK_NOFOLLOW
33#endif
34
35
36string
37get_current_working_dir()
38{
39 char buffer[4096];
40 if (!getcwd(buffer, 4096))
41 {
42 const int err = errno;
43 E(false, F("cannot get working directory: %s") % os_strerror(err));
44 }
45 return string(buffer);
46}
47
48void
49change_current_working_dir(string const & to)
50{
51 if (chdir(to.c_str()))
52 {
53 const int err = errno;
54 E(false, F("cannot change to directory %s: %s") % to % os_strerror(err));
55 }
56}
57
58string
59get_default_confdir()
60{
61 return get_homedir() + "/.monotone";
62}
63
64// FIXME: BUG: this probably mangles character sets
65// (as in, we're treating system-provided data as utf8, but it's probably in
66// the filesystem charset)
67string
68get_homedir()
69{
70 char * home = getenv("HOME");
71 if (home != NULL)
72 return string(home);
73
74 struct passwd * pw = getpwuid(getuid());
75 N(pw != NULL, F("could not find home directory for uid %d") % getuid());
76 return string(pw->pw_dir);
77}
78
79string
80tilde_expand(string const & in)
81{
82 if (in.empty() || in[0] != '~')
83 return in;
84 if (in.size() == 1) // just ~
85 return get_homedir();
86 if (in[1] == '/') // ~/...
87 return get_homedir() + in.substr(1);
88
89 string user, after;
90 string::size_type slashpos = in.find('/');
91 if (slashpos == string::npos)
92 {
93 user = in.substr(1);
94 after = "";
95 }
96 else
97 {
98 user = in.substr(1, slashpos-1);
99 after = in.substr(slashpos);
100 }
101
102 struct passwd * pw;
103 // FIXME: BUG: this probably mangles character sets (as in, we're
104 // treating system-provided data as utf8, but it's probably in the
105 // filesystem charset)
106 pw = getpwnam(user.c_str());
107 N(pw != NULL,
108 F("could not find home directory for user %s") % user);
109
110 return string(pw->pw_dir) + after;
111}
112
113path::status
114get_path_status(string const & path)
115{
116 struct stat buf;
117 int res;
118 res = stat(path.c_str(), &buf);
119 if (res < 0)
120 {
121 const int err = errno;
122 if (err == ENOENT)
123 return path::nonexistent;
124 else
125 E(false, F("error accessing file %s: %s") % path % os_strerror(err));
126 }
127 if (S_ISREG(buf.st_mode))
128 return path::file;
129 else if (S_ISDIR(buf.st_mode))
130 return path::directory;
131 else
132 {
133 // fifo or device or who knows what...
134 E(false, F("cannot handle special file %s") % path);
135 }
136}
137
138namespace
139{
140 // RAII object for DIRs.
141 struct dirhandle
142 {
143 dirhandle(string const & path)
144 {
145 d = opendir(path.c_str());
146 if (!d)
147 {
148 const int err = errno;
149 E(false, F("could not open directory '%s': %s") % path % os_strerror(err));
150 }
151 }
152 // technically closedir can fail, but there's nothing we could do about it.
153 ~dirhandle() { closedir(d); }
154
155 // accessors
156 struct dirent * next() { return readdir(d); }
157#ifdef HAVE_DIRFD
158 int fd() { return dirfd(d); }
159#endif
160 private:
161 DIR *d;
162 };
163}
164
165void
166do_read_directory(string const & path,
167 dirent_consumer & files,
168 dirent_consumer & dirs,
169 dirent_consumer & specials)
170{
171 string p(path);
172 if (p == "")
173 p = ".";
174
175 dirhandle dir(p);
176 struct dirent *d;
177 struct stat st;
178 int st_result;
179
180 while ((d = dir.next()) != 0)
181 {
182 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
183 continue;
184#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
185 switch (d->d_type)
186 {
187 case DT_REG: // regular file
188 files.consume(d->d_name);
189 continue;
190 case DT_DIR: // directory
191 dirs.consume(d->d_name);
192 continue;
193
194 case DT_UNKNOWN: // unknown type
195 case DT_LNK: // symlink - must find out what's at the other end
196 default:
197 break;
198 }
199#endif
200
201 // the use of stat rather than lstat here is deliberate.
202#if defined HAVE_FSTATAT && defined HAVE_DIRFD
203 {
204 static bool fstatat_works = true;
205 if (fstatat_works)
206 {
207 st_result = fstatat(dir.fd(), d->d_name, &st, 0);
208 if (st_result == -1 && errno == ENOSYS)
209 fstatat_works = false;
210 }
211 if (!fstatat_works)
212 st_result = stat((p + "/" + d->d_name).c_str(), &st);
213 }
214#else
215 st_result = stat((p + "/" + d->d_name).c_str(), &st);
216#endif
217
218 // if we get no entry it might be a broken symlink
219 // try again with lstat
220 if (st_result < 0 && errno == ENOENT)
221 {
222#if defined HAVE_FSTATAT && defined HAVE_DIRFD && defined AT_SYMLNK_NOFOLLOW
223 static bool fstatat_works = true;
224 if (fstatat_works)
225 {
226 st_result = fstatat(dir.fd(), d->d_name, &st, AT_SYMLNK_NOFOLLOW);
227 if (st_result == -1 && errno == ENOSYS)
228 fstatat_works = false;
229 }
230 if (!fstatat_works)
231 st_result = lstat((p + "/" + d->d_name).c_str(), &st);
232#else
233 st_result = lstat((p + "/" + d->d_name).c_str(), &st);
234#endif
235 }
236
237 int err = errno;
238
239 E(st_result == 0,
240 F("error accessing '%s/%s': %s") % p % d->d_name % os_strerror(err));
241
242 if (S_ISREG(st.st_mode))
243 files.consume(d->d_name);
244 else if (S_ISDIR(st.st_mode))
245 dirs.consume(d->d_name);
246 else if (S_ISLNK(st.st_mode))
247 files.consume(d->d_name); // treat broken links as files
248 else
249 specials.consume(d->d_name);
250 }
251 return;
252}
253
254
255
256void
257rename_clobberingly(string const & from, string const & to)
258{
259 if (rename(from.c_str(), to.c_str()))
260 {
261 const int err = errno;
262 E(false, F("renaming '%s' to '%s' failed: %s") % from % to % os_strerror(err));
263 }
264}
265
266// the C90 remove() function is guaranteed to work for both files and
267// directories
268void
269do_remove(string const & path)
270{
271 if (remove(path.c_str()))
272 {
273 const int err = errno;
274 E(false, F("could not remove '%s': %s") % path % os_strerror(err));
275 }
276}
277
278// Create the directory DIR. It will be world-accessible modulo umask.
279// Caller is expected to check for the directory already existing.
280void
281do_mkdir(string const & path)
282{
283 if (mkdir(path.c_str(), 0777))
284 {
285 const int err = errno;
286 E(false, F("could not create directory '%s': %s") % path % os_strerror(err));
287 }
288}
289
290// Create a temporary file in directory DIR, writing its name to NAME and
291// returning a read-write file descriptor for it. If unable to create
292// the file, throws an E().
293//
294
295// N.B. None of the standard temporary-file creation routines in libc do
296// what we want (mkstemp almost does, but it doesn't let us specify the
297// mode). This logic borrowed from libiberty's mkstemps(). To avoid grief
298// with case-insensitive file systems (*cough* OSX) we use only lowercase
299// letters for the name. This reduces the number of possible temporary
300// files from 62**6 to 36**6, oh noes.
301
302static int
303make_temp_file(string const & dir, string & name, mode_t mode)
304{
305 static const char letters[]
306 = "abcdefghijklmnopqrstuvwxyz0123456789";
307
308 const u32 base = sizeof letters - 1;
309 const u32 limit = base*base*base * base*base*base;
310
311 static u32 value;
312 struct timeval tv;
313 string tmp = dir + "/mtxxxxxx.tmp";
314
315 gettimeofday(&tv, 0);
316 value += ((u32) tv.tv_usec << 16) ^ tv.tv_sec ^ getpid();
317 value %= limit;
318
319 for (u32 i = 0; i < limit; i++)
320 {
321 u32 v = value;
322
323 tmp.at(tmp.size() - 10) = letters[v % base];
324 v /= base;
325 tmp.at(tmp.size() - 9) = letters[v % base];
326 v /= base;
327 tmp.at(tmp.size() - 8) = letters[v % base];
328 v /= base;
329 tmp.at(tmp.size() - 7) = letters[v % base];
330 v /= base;
331 tmp.at(tmp.size() - 6) = letters[v % base];
332 v /= base;
333 tmp.at(tmp.size() - 5) = letters[v % base];
334 v /= base;
335
336 int fd = open(tmp.c_str(), O_RDWR|O_CREAT|O_EXCL, mode);
337 int err = errno;
338
339 if (fd >= 0)
340 {
341 name = tmp;
342 return fd;
343 }
344
345 // EEXIST means we should go 'round again. Any other errno value is a
346 // plain error. (ENOTDIR is a bug, and so are some ELOOP and EACCES
347 // conditions - caller's responsibility to make sure that 'dir' is in
348 // fact a directory to which we can write - but we get better
349 // diagnostics from this E() than we would from an I().)
350
351 E(err == EEXIST,
352 F("cannot create temp file %s: %s") % tmp % os_strerror(err));
353
354 // This increment is relatively prime to 'limit', therefore 'value'
355 // will visit every number in its range.
356 value += 7777;
357 value %= limit;
358 }
359
360 // we really should never get here.
361 E(false, F("all %d possible temporary file names are in use") % limit);
362}
363
364
365// Write string DAT atomically to file FNAME, using TMP as the location to
366// create a file temporarily. rename(2) from an arbitrary filename in TMP
367// to FNAME must work (i.e. they must be on the same filesystem).
368// If USER_PRIVATE is true, the file will be potentially accessible only to
369// the user, else it will be potentially accessible to everyone (i.e. open()
370// will be passed mode 0600 or 0666 -- the actual permissions are modified
371// by umask as usual).
372void
373write_data_worker(string const & fname,
374 string const & dat,
375 string const & tmpdir,
376 bool user_private)
377{
378 struct auto_closer
379 {
380 int fd;
381 auto_closer(int fd) : fd(fd) {}
382 ~auto_closer() { close(fd); }
383 };
384
385 string tmp;
386 int fd = make_temp_file(tmpdir, tmp, user_private ? 0600 : 0666);
387
388 {
389 auto_closer guard(fd);
390
391 char const * ptr = dat.data();
392 size_t remaining = dat.size();
393 int deadcycles = 0;
394
395 L(FL("writing %s via temp %s") % fname % tmp);
396
397 do
398 {
399 ssize_t written = write(fd, ptr, remaining);
400 const int err = errno;
401 E(written >= 0,
402 F("error writing to temp file %s: %s") % tmp % os_strerror(err));
403 if (written == 0)
404 {
405 deadcycles++;
406 E(deadcycles < 4,
407 FP("giving up after four zero-length writes to %s "
408 "(%d byte written, %d left)",
409 "giving up after four zero-length writes to %s "
410 "(%d bytes written, %d left)",
411 ptr - dat.data())
412 % tmp % (ptr - dat.data()) % remaining);
413 }
414 ptr += written;
415 remaining -= written;
416 }
417 while (remaining > 0);
418 }
419 // fd is now closed
420
421 rename_clobberingly(tmp, fname);
422}
423
424string
425get_locale_dir()
426{
427 return string(LOCALEDIR);
428}
429
430// Local Variables:
431// mode: C++
432// fill-column: 76
433// c-file-style: "gnu"
434// indent-tabs-mode: nil
435// End:
436// 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