source: trunk/CORE/arb_file.cxx

Last change on this file was 18730, checked in by westram, 3 years ago
  • remove trailing whitespace from c source.
File size: 17.1 KB
Line 
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
28long 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
34long 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
44unsigned 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
50GB_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
60long 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
68long 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
76bool 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
83bool 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
89bool 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
95bool 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
101bool 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
124bool 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
146inline bool mode_is_user_writeable(long mode) { return mode>0 && (mode & S_IWUSR); }
147
148bool 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
161static 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
172bool GB_is_readablefile(const char *filename) {
173    return !GB_is_directory(filename) && GB_is_readable(filename);
174}
175
176bool 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
182long 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
188int GB_unlink(const char *path) {
189    /*! unlink a file
190     * @return
191     *  0   success
192     *  1   File did not exist
193     * -1   Error (use GB_await_error() to retrieve message)
194     */
195
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
206void 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
221GB_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
231GB_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
262char *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
284GB_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
302GB_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
332GB_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}
342GB_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
360void 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);
366    TEST_EXPECT_DIFFERENT(GB_mode_of_file(someDir), -1);
367    TEST_EXPECT_EQUAL(GB_mode_of_file(noFile), -1);
368    TEST_EXPECT_EQUAL(GB_mode_of_file(NULp), -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));
389        TEST_REJECT(GB_is_link(NULp));
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));
396        TEST_REJECT(GB_is_regularfile(noFile));
397        TEST_REJECT(GB_is_regularfile(NULp));
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));
405        TEST_REJECT(GB_is_directory(NULp));
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));
411        TEST_REJECT(GB_is_readablefile(noFile));
412        TEST_REJECT(GB_is_readablefile(someDir));
413        TEST_REJECT(GB_is_readablefile(NULp));
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));
419        TEST_EXPECT_DIFFERENT(GB_mode_of_link(linkToDir), GB_mode_of_file(someDir));
420        TEST_EXPECT_DIFFERENT(GB_mode_of_link(linkNowhere), -1);
421        TEST_EXPECT_EQUAL(GB_mode_of_link(NULp), -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
432void 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)
445    TEST_EXPECT_DIFFERENT(GB_unlink(f2), -1);
446
447    TEST_REJECT(GB_is_regularfile(f1));
448    TEST_REJECT(GB_is_regularfile(f2));
449
450    TEST_EXPECT_NO_ERROR(GB_copy_file(smallerFile, f1));
451    TEST_EXPECT_EQUAL(GB_size_of_file(f1), small);
452    TEST_EXPECT_MODE_EQUAL(GB_mode_of_file(f1), mode);
453
454    TEST_EXPECT_NO_ERROR(GB_copy_file(biggerFile, f2));
455    TEST_EXPECT_EQUAL(GB_size_of_file(f2), big);
456    TEST_EXPECT_ERROR_CONTAINS(GB_safe_rename_file(f1, f2), "already exists"); // rename does not overwrite
457    TEST_EXPECT_EQUAL(GB_size_of_file(f1), small);
458    TEST_EXPECT_EQUAL(GB_size_of_file(f2), big);
459
460    TEST_EXPECT_NO_ERROR(GB_move_file(f1, f2));
461    TEST_EXPECT_EQUAL(GB_size_of_file(f1), -1);
462    TEST_EXPECT_EQUAL(GB_size_of_file(f2), small);
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");
471    TEST_REJECT(GB_is_regularfile(f1));
472    TEST_EXPECT(GB_is_regularfile(f2));
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)
484    TEST_EXPECT_EQUAL(GB_size_of_file(f1), -1);
485    TEST_EXPECT_EQUAL(GB_size_of_file(f2), small);
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");
493    TEST_EXPECT_EQUAL(GB_size_of_file(f1), -1);
494    TEST_EXPECT_EQUAL(GB_size_of_file(f2), small);
495
496    TEST_EXPECT_NO_ERROR(GB_move_file(f2, f1)); // overwrite with unreadable file
497    TEST_EXPECT_EQUAL(GB_size_of_file(f1), small);
498    TEST_EXPECT_EQUAL(GB_size_of_file(f2), -1);
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");
505    TEST_EXPECT_EQUAL(GB_size_of_file(f1), small);
506    TEST_EXPECT_EQUAL(GB_size_of_file(f2), -1);
507
508    TEST_EXPECT_DIFFERENT(GB_unlink(f1), -1);
509
510    // --------------------
511    //      test bugs:
512
513    TEST_EXPECT_NO_ERROR(GB_copy_file(biggerFile, f1));
514    TEST_EXPECT_EQUAL(GB_size_of_file(f1), big);
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
518    TEST_EXPECT_DIFFERENT(GB_unlink(f1), -1);
519
520    // check cleaned-up
521    TEST_REJECT(GB_is_regularfile(f1));
522    TEST_REJECT(GB_is_regularfile(f2));
523    TEST_REJECT(GB_is_regularfile(noFile));
524}
525TEST_PUBLISH(TEST_basic_file_ops);
526
527#endif // UNIT_TESTS
528
529// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.