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