ARB
arb_file.cxx
Go to the documentation of this file.
1 // =============================================================== //
2 // //
3 // File : arb_file.cxx //
4 // Purpose : Basic file operations //
5 // //
6 // Coded by Ralf Westram (coder@reallysoft.de) in October 2011 //
7 // Institute of Microbiology (Technical University Munich) //
8 // http://www.arb-home.de/ //
9 // //
10 // =============================================================== //
11 
12 #include "arb_file.h"
13 #include "arb_string.h"
14 #include "arb_msg.h"
15 
16 #include <unistd.h>
17 #include <utime.h>
18 #include <sys/stat.h>
19 #include <fcntl.h>
20 
21 #include <cstdio>
22 #include <cerrno>
23 
24 // AISC_MKPT_PROMOTE:#ifndef ARB_CORE_H
25 // AISC_MKPT_PROMOTE:#include "arb_core.h"
26 // AISC_MKPT_PROMOTE:#endif
27 
28 long GB_size_of_file(const char *path) {
29  struct stat stt;
30  if (!path || stat(path, &stt)) return -1;
31  return stt.st_size;
32 }
33 
34 long GB_size_of_FILE(FILE *in) {
35  int fi = fileno(in);
36  struct stat st;
37  if (fstat(fi, &st)) {
38  GB_export_error("GB_size_of_FILE: sorry file is not readable");
39  return -1;
40  }
41  return st.st_size;
42 }
43 
44 unsigned long GB_time_of_file(const char *path) {
45  struct stat stt;
46  if (!path || stat(path, &stt)) return 0; // return epoch for missing files
47  return stt.st_mtime;
48 }
49 
50 GB_ERROR GB_set_time_of_file(const char *path, unsigned long new_time) {
51  utimbuf ut;
52 
53  ut.actime = new_time;
54  ut.modtime = new_time;
55 
56  int res = utime(path, &ut);
57  return res ? GB_IO_error("setting timestamp of", path) : NULp;
58 }
59 
60 long GB_mode_of_file(const char *path) {
61  if (path) {
62  struct stat stt;
63  if (stat(path, &stt) == 0) return stt.st_mode;
64  }
65  return -1;
66 }
67 
68 long GB_mode_of_link(const char *path) {
69  if (path) {
70  struct stat stt;
71  if (lstat(path, &stt) == 0) return stt.st_mode;
72  }
73  return -1;
74 }
75 
76 bool GB_is_regularfile(const char *path) {
77  // Warning : returns true for symbolic links to files (use GB_is_link() to test)
78  if (!path) return false;
79  struct stat stt;
80  return stat(path, &stt) == 0 && S_ISREG(stt.st_mode);
81 }
82 
83 bool GB_is_link(const char *path) {
84  if (!path) return false;
85  struct stat stt;
86  return lstat(path, &stt) == 0 && S_ISLNK(stt.st_mode);
87 }
88 
89 bool GB_is_fifo(const char *path) {
90  if (!path) return false;
91  struct stat stt;
92  return stat(path, &stt) == 0 && S_ISFIFO(stt.st_mode);
93 }
94 
95 bool GB_is_fifo(FILE *fp) {
96  if (!fp) return false;
97  struct stat stt;
98  return fstat(fileno(fp), &stt) == 0 && S_ISFIFO(stt.st_mode);
99 }
100 
101 bool GB_is_executablefile(const char *path) {
102  struct stat stt;
103  bool executable = false;
104 
105  if (stat(path, &stt) == 0) {
106  uid_t my_userid = geteuid(); // effective user id
107  if (stt.st_uid == my_userid) { // I am the owner of the file
108  executable = !!(stt.st_mode&S_IXUSR); // owner execution permission
109  }
110  else {
111  gid_t my_groupid = getegid(); // effective group id
112  if (stt.st_gid == my_groupid) { // I am member of the file's group
113  executable = !!(stt.st_mode&S_IXGRP); // group execution permission
114  }
115  else {
116  executable = !!(stt.st_mode&S_IXOTH); // others execution permission
117  }
118  }
119  }
120 
121  return executable;
122 }
123 
124 bool GB_is_privatefile(const char *path, bool read_private) {
125  // return true, if nobody but user has write permission
126  // if 'read_private' is true, only return true if nobody but user has read permission
127  //
128  // Note: Always returns true for missing files!
129  //
130  // GB_is_privatefile is mainly used to assert that files generated in /tmp have secure permissions
131 
132  struct stat stt;
133  bool isprivate = true;
134 
135  if (stat(path, &stt) == 0) {
136  if (read_private) {
137  isprivate = (stt.st_mode & (S_IWGRP|S_IWOTH|S_IRGRP|S_IROTH)) == 0;
138  }
139  else {
140  isprivate = (stt.st_mode & (S_IWGRP|S_IWOTH)) == 0;
141  }
142  }
143  return isprivate;
144 }
145 
146 inline bool mode_is_user_writeable(long mode) { return mode>0 && (mode & S_IWUSR); }
147 
148 bool GB_is_writeablefile(const char *filename) { // for user
149  bool writable = false;
150  if (GB_is_regularfile(filename)) {
151  writable = mode_is_user_writeable(GB_mode_of_file(filename));
152  if (writable && GB_is_link(filename)) {
153  char *target = GB_follow_unix_link(filename);
154  writable = GB_is_writeablefile(target);
155  free(target);
156  }
157  }
158  return writable;
159 }
160 
161 static bool GB_is_readable(const char *file_or_dir) {
162  if (file_or_dir) {
163  FILE *in = fopen(file_or_dir, "r");
164  if (in) {
165  fclose(in);
166  return true;
167  }
168  }
169  return false;
170 }
171 
172 bool GB_is_readablefile(const char *filename) {
173  return !GB_is_directory(filename) && GB_is_readable(filename);
174 }
175 
176 bool GB_is_directory(const char *path) {
177  // Warning : returns true for symbolic links to directories (use GB_is_link())
178  struct stat stt;
179  return path && stat(path, &stt) == 0 && S_ISDIR(stt.st_mode);
180 }
181 
182 long GB_getuid_of_file(const char *path) {
183  struct stat stt;
184  if (stat(path, &stt)) return -1;
185  return stt.st_uid;
186 }
187 
188 int GB_unlink(const char *path) {
196  if (unlink(path) != 0) {
197  if (errno == ENOENT) {
198  return 1;
199  }
200  GB_export_error(GB_IO_error("removing", path));
201  return -1;
202  }
203  return 0;
204 }
205 
206 void GB_unlink_or_warn(const char *path, GB_ERROR *error) {
207  /* Unlinks 'path'
208  *
209  * In case of a real unlink failure:
210  * - if 'error' is given -> set error if not already set
211  * - otherwise only warn
212  */
213 
214  if (GB_unlink(path)<0) {
215  GB_ERROR unlink_error = GB_await_error();
216  if (error && !*error) *error = unlink_error;
217  else GB_warning(unlink_error);
218  }
219 }
220 
221 GB_ERROR GB_symlink(const char *target, const char *link) {
222  GB_ERROR error = NULp;
223  if (symlink(target, link)<0) {
224  char *what = GBS_global_string_copy("creating symlink (to file '%s')", target);
225  error = GB_IO_error(what, link);
226  free(what);
227  }
228  return error;
229 }
230 
231 GB_ERROR GB_set_mode_of_file(const char *path, long mode) {
232  /*
233  Patch from Alan McCulloch:
234 
235  get user, group,other read, write and execute
236  permissions and if these are the same in the
237  existing and requested modes of the file ,
238  don't chmod (gets around "Cannot set mode" errors
239  which will happen if user does not own file)
240 
241  This assumes that the requested permission change is not
242  outside the mask S_IRWXU | S_IRWXG | S_IRWXO - if it is, then
243  the requested change will not be made
244  */
245 
246  int permissions_mask = S_IRWXU | S_IRWXG | S_IRWXO ;
247  struct stat sb;
248 
249 
250  if (stat(path, &sb) == -1) {
251  return GBS_global_string("Cannot get existing mode of '%s'", path);
252  }
253 
254  if (((int)sb.st_mode & permissions_mask) == ((int)mode & permissions_mask)) {
255  return NULp;
256  }
257 
258  if (chmod(path, (int)mode)) return GB_IO_error("changing mode of", path);
259  return NULp;
260 }
261 
262 char *GB_follow_unix_link(const char *path) { // returns the real path of a file
263  char buffer[1000];
264  char *path2;
265  char *pos;
266  char *res;
267  int len = readlink(path, buffer, 999);
268  if (len<0) return NULp;
269  buffer[len] = 0;
270  if (path[0] == '/') return ARB_strdup(buffer);
271 
272  path2 = ARB_strdup(path);
273  pos = strrchr(path2, '/');
274  if (!pos) {
275  free(path2);
276  return ARB_strdup(buffer);
277  }
278  *pos = 0;
279  res = GBS_global_string_copy("%s/%s", path2, buffer);
280  free(path2);
281  return res;
282 }
283 
284 GB_ERROR GB_move_file(const char *oldpath, const char *newpath) {
285  // Warning: unconditionally overwrites existing destination (even if write-protected!)
286  // Use GB_safe_rename_file() to avoid overwrites.
287 
288  long old_mod = GB_mode_of_file(newpath); // keep filemode for existing files
289  if (old_mod == -1) old_mod = GB_mode_of_file(oldpath);
290 
291  GB_ERROR error = NULp;
292  if (rename(oldpath, newpath) != 0) {
293  error = GB_IO_error("renaming", GBS_global_string("%s' into '%s", oldpath, newpath)); // Note: GB_IO_error quotes it's 2nd arg
294  }
295  else {
296  error = GB_set_mode_of_file(newpath, old_mod);
297  }
298 
299  return error;
300 }
301 
302 GB_ERROR GB_copy_file(const char *srcpath, const char *dstpath) {
303  GB_ERROR error = NULp;
304 
305  int src = open(srcpath, O_RDONLY, 0);
306  if (src == -1) error = GB_IO_error("reading", srcpath);
307  else {
308  int dst = open(dstpath, O_WRONLY | O_CREAT | O_TRUNC, 0644);
309  if (dst == -1) error = GB_IO_error("writing", dstpath);
310  else {
311  char buf[BUFSIZ]; // defined by stdio
312 
313  while (!error) {
314  ssize_t got = read(src, buf, BUFSIZ);
315  if (got == -1) error = GB_IO_error("reading", srcpath);
316  else {
317  ssize_t wrote = write(dst, buf, got);
318  if (wrote == -1) error = GB_IO_error("writing", dstpath);
319  else {
320  arb_assert(wrote == got);
321  if (!wrote) break; // done
322  }
323  }
324  }
325  close(dst);
326  }
327  close(src);
328  }
329  return error;
330 }
331 
332 GB_ERROR GB_safe_rename_file(const char *oldpath, const char *newpath) { // replacement for GB_rename_file
333  GB_ERROR error = NULp;
334  if (GB_is_regularfile(newpath)) {
335  error = "file already exists";
336  }
337  else {
338  error = GB_move_file(oldpath, newpath);
339  }
340  return error;
341 }
342 GB_ERROR GB_safe_copy_file(const char *oldpath, const char *newpath) { // non-overwriting copy
343  GB_ERROR error = NULp;
344  if (GB_is_regularfile(newpath)) {
345  error = "file already exists";
346  }
347  else {
348  error = GB_copy_file(oldpath, newpath);
349  }
350  return error;
351 }
352 
353 // --------------------------------------------------------------------------------
354 
355 #ifdef UNIT_TESTS
356 #ifndef TEST_UNIT_H
357 #include <test_unit.h>
358 #endif
359 
360 void TEST_basic_file_checks() {
361  const char *someDir = "general";
362  const char *someFile = "general/text.input";
363  const char *noFile = "general/nosuch.input";
364 
365  TEST_EXPECT_DIFFERENT(GB_mode_of_file(someFile), -1);
367  TEST_EXPECT_EQUAL(GB_mode_of_file(noFile), -1);
369 
370  {
371  const char *linkToFile = "fileLink";
372  const char *linkToDir = "dirLink";
373  const char *linkNowhere = "brokenLink";
374 
375  TEST_EXPECT_DIFFERENT(GB_unlink(linkToFile), -1);
376  TEST_EXPECT_DIFFERENT(GB_unlink(linkNowhere), -1);
377  TEST_EXPECT_DIFFERENT(GB_unlink(linkToDir), -1);
378 
379  TEST_EXPECT_NO_ERROR(GB_symlink(someFile, linkToFile));
380  TEST_EXPECT_NO_ERROR(GB_symlink(someDir, linkToDir));
381  TEST_EXPECT_NO_ERROR(GB_symlink(noFile, linkNowhere));
382 
383  TEST_EXPECT(GB_is_link(linkToFile));
384  TEST_EXPECT(GB_is_link(linkToDir));
385  TEST_EXPECT(GB_is_link(linkNowhere));
386  TEST_REJECT(GB_is_link(someFile));
387  TEST_REJECT(GB_is_link(noFile));
388  TEST_REJECT(GB_is_link(someDir));
390 
391  TEST_EXPECT(GB_is_regularfile(linkToFile));
392  TEST_REJECT(GB_is_regularfile(linkToDir));
393  TEST_REJECT(GB_is_regularfile(linkNowhere));
394  TEST_EXPECT(GB_is_regularfile(someFile));
395  TEST_REJECT(GB_is_regularfile(someDir));
398 
399  TEST_REJECT(GB_is_directory(linkToFile));
400  TEST_EXPECT(GB_is_directory(linkToDir));
401  TEST_REJECT(GB_is_directory(linkNowhere));
402  TEST_REJECT(GB_is_directory(someFile));
403  TEST_REJECT(GB_is_directory(noFile));
404  TEST_EXPECT(GB_is_directory(someDir));
406 
407  TEST_EXPECT(GB_is_readablefile(linkToFile));
408  TEST_REJECT(GB_is_readablefile(linkToDir));
409  TEST_REJECT(GB_is_readablefile(linkNowhere));
410  TEST_EXPECT(GB_is_readablefile(someFile));
414 
415  TEST_EXPECT(GB_is_readable(linkToDir));
416  TEST_EXPECT(GB_is_readable(someDir));
417 
418  TEST_EXPECT_DIFFERENT(GB_mode_of_link(linkToFile), GB_mode_of_file(someFile));
420  TEST_EXPECT_DIFFERENT(GB_mode_of_link(linkNowhere), -1);
422 
423  TEST_EXPECT_DIFFERENT(GB_unlink(linkToFile), -1);
424  TEST_EXPECT_DIFFERENT(GB_unlink(linkToDir), -1);
425  TEST_EXPECT_DIFFERENT(GB_unlink(linkNowhere), -1);
426  }
427 }
428 
429 #define MODE_MASK 0xFFC0
430 #define TEST_EXPECT_MODE_EQUAL(got,expd) TEST_EXPECT_EQUAL((got)&MODE_MASK, (expd)&MODE_MASK)
431 
432 void TEST_basic_file_ops() {
433  const char *smallerFile = "general/mac.input";
434  const char *biggerFile = "general/dos.input";
435  const char *f1 = "general/tmp1.input";
436  const char *f2 = "general/tmp2.input";
437  const char *noFile = "general/nosuch.input";
438 
439  long small = GB_size_of_file(smallerFile);
440  long big = GB_size_of_file(biggerFile);
441 
442  long mode = GB_mode_of_file(smallerFile);
443 
444  TEST_EXPECT_DIFFERENT(GB_unlink(f1), -1); // delete destination files (probably present if test failed b4)
446 
449 
450  TEST_EXPECT_NO_ERROR(GB_copy_file(smallerFile, f1));
452  TEST_EXPECT_MODE_EQUAL(GB_mode_of_file(f1), mode);
453 
454  TEST_EXPECT_NO_ERROR(GB_copy_file(biggerFile, f2));
456  TEST_EXPECT_ERROR_CONTAINS(GB_safe_rename_file(f1, f2), "already exists"); // rename does not overwrite
459 
463  TEST_EXPECT_MODE_EQUAL(GB_mode_of_file(f2), mode);
464 
465  // ------------------------
466  // test failures:
467 
468  // attempt to copy non-existing file
469  TEST_EXPECT_ERROR_CONTAINS(GB_copy_file(noFile, f1), "No such file");
470  TEST_EXPECT_ERROR_CONTAINS(GB_copy_file(noFile, f2), "No such file");
473 
474  TEST_EXPECT_ERROR_CONTAINS(GB_safe_copy_file(smallerFile, f2), "already exists"); // safe-copy does not overwrite
475 
476  // attempt to copy/move over write-protected dest-file
477  long mode_wprot = mode & ~(S_IWUSR | S_IWGRP | S_IWOTH);
478  TEST_EXPECT_NO_ERROR(GB_set_mode_of_file(f2, mode_wprot)); // remove write-permission
479 
480  TEST_EXPECT_ERROR_CONTAINS(GB_copy_file(smallerFile, f2), "Permission denied");
481 
482  TEST_EXPECT_NO_ERROR(GB_copy_file(smallerFile, f1));
483  TEST_EXPECT_NO_ERROR(GB_move_file(f1, f2)); // overwrites existing file (even if write-protected)
486  TEST_EXPECT_MODE_EQUAL(GB_mode_of_file(f2), mode_wprot); // and keeps mode of destination
487 
488  // attempt to copy/move unreadable source-file
489  long mode_unread = mode & ~(S_IRUSR | S_IRGRP | S_IROTH);
490  TEST_EXPECT_NO_ERROR(GB_set_mode_of_file(f2, mode_unread)); // remove read-permission
491 
492  TEST_EXPECT_ERROR_CONTAINS(GB_copy_file(f2, f1), "Permission denied");
495 
496  TEST_EXPECT_NO_ERROR(GB_move_file(f2, f1)); // overwrite with unreadable file
499  TEST_EXPECT_MODE_EQUAL(GB_mode_of_file(f1), mode_unread); // and keeps mode of destination
500 
501  TEST_EXPECT_NO_ERROR(GB_set_mode_of_file(f1, mode)); // restore mode
502 
503  // attempt to move non-existing file
504  TEST_EXPECT_ERROR_CONTAINS(GB_move_file(f2, f1), "No such file");
507 
509 
510  // --------------------
511  // test bugs:
512 
513  TEST_EXPECT_NO_ERROR(GB_copy_file(biggerFile, f1));
515  TEST_EXPECT_NO_ERROR(GB_copy_file(smallerFile, f1)); // overwrite bigger with smaller file
516  TEST_EXPECT_EQUAL(GB_size_of_file(f1), small); // truncates correctly
517 
519 
520  // check cleaned-up
524 }
525 TEST_PUBLISH(TEST_basic_file_ops);
526 
527 #endif // UNIT_TESTS
528 
529 // --------------------------------------------------------------------------------
#define arb_assert(cond)
Definition: arb_assert.h:245
const char * GB_ERROR
Definition: arb_core.h:25
long GB_size_of_FILE(FILE *in)
Definition: arb_file.cxx:34
bool GB_is_executablefile(const char *path)
Definition: arb_file.cxx:101
void GB_warning(const char *message)
Definition: arb_msg.cxx:530
GB_ERROR GB_safe_copy_file(const char *oldpath, const char *newpath)
Definition: arb_file.cxx:342
long GB_mode_of_link(const char *path)
Definition: arb_file.cxx:68
void GB_unlink_or_warn(const char *path, GB_ERROR *error)
Definition: arb_file.cxx:206
GB_ERROR GB_copy_file(const char *srcpath, const char *dstpath)
Definition: arb_file.cxx:302
long GB_mode_of_file(const char *path)
Definition: arb_file.cxx:60
GB_ERROR GB_symlink(const char *target, const char *link)
Definition: arb_file.cxx:221
GB_ERROR GB_IO_error(const char *action, const char *filename)
Definition: arb_msg.cxx:285
char * ARB_strdup(const char *str)
Definition: arb_string.h:27
const char * GBS_global_string(const char *templat,...)
Definition: arb_msg.cxx:203
long GB_getuid_of_file(const char *path)
Definition: arb_file.cxx:182
int GB_unlink(const char *path)
Definition: arb_file.cxx:188
bool mode_is_user_writeable(long mode)
Definition: arb_file.cxx:146
long GB_size_of_file(const char *path)
Definition: arb_file.cxx:28
GB_ERROR GB_set_time_of_file(const char *path, unsigned long new_time)
Definition: arb_file.cxx:50
char buffer[MESSAGE_BUFFERSIZE]
Definition: seq_search.cxx:34
GB_ERROR GB_safe_rename_file(const char *oldpath, const char *newpath)
Definition: arb_file.cxx:332
#define TEST_PUBLISH(testfunction)
Definition: test_unit.h:1517
GB_ERROR GB_export_error(const char *error)
Definition: arb_msg.cxx:257
GB_ERROR GB_await_error()
Definition: arb_msg.cxx:342
#define TEST_EXPECT(cond)
Definition: test_unit.h:1328
unsigned long GB_time_of_file(const char *path)
Definition: arb_file.cxx:44
GB_ERROR GB_move_file(const char *oldpath, const char *newpath)
Definition: arb_file.cxx:284
#define TEST_REJECT(cond)
Definition: test_unit.h:1330
static void error(const char *msg)
Definition: mkptypes.cxx:96
bool GB_is_fifo(const char *path)
Definition: arb_file.cxx:89
GB_ERROR GB_set_mode_of_file(const char *path, long mode)
Definition: arb_file.cxx:231
bool GB_is_writeablefile(const char *filename)
Definition: arb_file.cxx:148
aisc_com * link
bool GB_is_directory(const char *path)
Definition: arb_file.cxx:176
#define TEST_EXPECT_NO_ERROR(call)
Definition: test_unit.h:1118
#define NULp
Definition: cxxforward.h:116
bool GB_is_regularfile(const char *path)
Definition: arb_file.cxx:76
bool GB_is_readablefile(const char *filename)
Definition: arb_file.cxx:172
#define TEST_EXPECT_ERROR_CONTAINS(call, part)
Definition: test_unit.h:1114
#define TEST_EXPECT_DIFFERENT(expr, want)
Definition: test_unit.h:1301
bool GB_is_link(const char *path)
Definition: arb_file.cxx:83
char * GB_follow_unix_link(const char *path)
Definition: arb_file.cxx:262
static bool GB_is_readable(const char *file_or_dir)
Definition: arb_file.cxx:161
#define TEST_EXPECT_EQUAL(expr, want)
Definition: test_unit.h:1294
char * GBS_global_string_copy(const char *templat,...)
Definition: arb_msg.cxx:194
bool GB_is_privatefile(const char *path, bool read_private)
Definition: arb_file.cxx:124