]> arthur.barton.de Git - netatalk.git/blob - libatalk/vfs/unix.c
ad cp nearly done
[netatalk.git] / libatalk / vfs / unix.c
1 /*
2  * Copyright (c) 1990,1993 Regents of The University of Michigan.
3  * All Rights Reserved.  See COPYRIGHT.
4  */
5
6 #ifdef HAVE_CONFIG_H
7 #include "config.h"
8 #endif /* HAVE_CONFIG_H */
9
10 #include <unistd.h>
11 #include <errno.h>
12 #include <stdlib.h>
13 #include <sys/param.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <string.h>
17
18 #include <atalk/afp.h>
19 #include <atalk/util.h>
20 #include <atalk/directory.h>
21 #include <atalk/volume.h>
22 #include <atalk/logger.h>
23 #include <atalk/unix.h>
24
25 /* -----------------------------
26    a dropbox is a folder where w is set but not r eg:
27    rwx-wx-wx or rwx-wx--
28    rwx----wx (is not asked by a Mac with OS >= 8.0 ?)
29 */
30 int stickydirmode(const char *name, const mode_t mode, const int dropbox, const mode_t v_umask)
31 {
32     int retval = 0;
33
34 #ifdef DROPKLUDGE
35     /* Turn on the sticky bit if this is a drop box, also turn off the setgid bit */
36     if ((dropbox & AFPVOL_DROPBOX)) {
37         int uid;
38
39         if ( ( (mode & S_IWOTH) && !(mode & S_IROTH)) ||
40              ( (mode & S_IWGRP) && !(mode & S_IRGRP)) )
41         {
42             uid=geteuid();
43             if ( seteuid(0) < 0) {
44                 LOG(log_error, logtype_afpd, "stickydirmode: unable to seteuid root: %s", strerror(errno));
45             }
46             if ( (retval=chmod( name, ( (DIRBITS | mode | S_ISVTX) & ~v_umask) )) < 0) {
47                 LOG(log_error, logtype_afpd, "stickydirmode: chmod \"%s\": %s", fullpathname(name), strerror(errno) );
48             } else {
49                 LOG(log_debug, logtype_afpd, "stickydirmode: chmod \"%s\": %s", fullpathname(name), strerror(retval) );
50             }
51             seteuid(uid);
52             return retval;
53         }
54     }
55 #endif /* DROPKLUDGE */
56
57     /*
58      *  Ignore EPERM errors:  We may be dealing with a directory that is
59      *  group writable, in which case chmod will fail.
60      */
61     if ( (chmod( name, (DIRBITS | mode) & ~v_umask ) < 0) && errno != EPERM &&
62          !(errno == ENOENT && (dropbox & AFPVOL_NOADOUBLE)) )
63     {
64         LOG(log_error, logtype_afpd, "stickydirmode: chmod \"%s\": %s", fullpathname(name), strerror(errno) );
65         retval = -1;
66     }
67
68     return retval;
69 }
70
71 /* ------------------------- */
72 int dir_rx_set(mode_t mode)
73 {
74     return (mode & (S_IXUSR | S_IRUSR)) == (S_IXUSR | S_IRUSR);
75 }
76
77 /* --------------------- */
78 int setfilmode(const char * name, mode_t mode, struct stat *st, mode_t v_umask)
79 {
80     struct stat sb;
81     mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO;  /* rwx for owner group and other, by default */
82
83     if (!st) {
84         if (lstat(name, &sb) != 0)
85             return -1;
86         st = &sb;
87     }
88
89     if (S_ISLNK(st->st_mode))
90         return 0; /* we don't want to change link permissions */
91     
92     mode |= st->st_mode & ~mask; /* keep other bits from previous mode */
93
94     if ( chmod( name,  mode & ~v_umask ) < 0 && errno != EPERM ) {
95         return -1;
96     }
97     return 0;
98 }
99
100 /*
101  * @brief system rmdir with afp error code.
102  *
103  * Supports *at semantics (cf openat) if HAVE_RENAMEAT. Pass dirfd=-1 to ignore this.
104  */
105 int netatalk_rmdir_all_errors(int dirfd, const char *name)
106 {
107     int err;
108
109 #ifdef HAVE_RENAMEAT
110     if (dirfd == -1)
111         dirfd = ATFD_CWD;
112     err = unlinkat(dirfd, name, AT_REMOVEDIR);
113 #else
114     err = rmdir(name);
115 #endif
116
117     if (err < 0) {
118         switch ( errno ) {
119         case ENOENT :
120             return AFPERR_NOOBJ;
121         case ENOTEMPTY :
122             return AFPERR_DIRNEMPT;
123         case EPERM:
124         case EACCES :
125             return AFPERR_ACCESS;
126         case EROFS:
127             return AFPERR_VLOCK;
128         default :
129             return AFPERR_PARAM;
130         }
131     }
132     return AFP_OK;
133 }
134
135 /*
136  * @brief System rmdir with afp error code, but ENOENT is not an error.
137  *
138  * Supports *at semantics (cf openat) if HAVE_RENAMEAT. Pass dirfd=-1 to ignore this.
139  */
140 int netatalk_rmdir(int dirfd, const char *name)
141 {
142     int ret = netatalk_rmdir_all_errors(dirfd, name);
143     if (ret == AFPERR_NOOBJ)
144         return AFP_OK;
145     return ret;
146 }
147
148 /* -------------------
149    system unlink with afp error code.
150    ENOENT is not an error.
151 */
152 int netatalk_unlink(const char *name)
153 {
154     if (unlink(name) < 0) {
155         switch (errno) {
156         case ENOENT :
157             break;
158         case EROFS:
159             return AFPERR_VLOCK;
160         case EPERM:
161         case EACCES :
162             return AFPERR_ACCESS;
163         default :
164             return AFPERR_PARAM;
165         }
166     }
167     return AFP_OK;
168 }
169
170 char *fullpathname(const char *name)
171 {
172     static char wd[ MAXPATHLEN + 1];
173
174     if ( getcwd( wd , MAXPATHLEN) ) {
175         strlcat(wd, "/", MAXPATHLEN);
176         strlcat(wd, name, MAXPATHLEN);
177     }
178     else {
179         strlcpy(wd, name, MAXPATHLEN);
180     }
181     return wd;
182 }
183
184
185 /**************************************************************************
186  * *at semnatics support functions (like openat, renameat standard funcs)
187  **************************************************************************/
188
189 /* 
190  * Supports *at semantics if HAVE_RENAMEAT, pass dirfd=-1 to ignore this
191  */
192 int copy_file(int dirfd, const char *src, const char *dst, mode_t mode)
193 {
194     int    ret = 0;
195     int    sfd = -1;
196     int    dfd = -1;
197     ssize_t cc;
198     size_t  buflen;
199     char   filebuf[8192];
200
201 #ifdef HAVE_RENAMEAT
202     if (dirfd == -1)
203         dirfd = ATFD_CWD;
204     sfd = openat(dirfd, src, O_RDONLY);
205 #else
206     sfd = open(src, O_RDONLY);
207 #endif
208     if (sfd < 0) {
209         LOG(log_error, logtype_afpd, "copy_file('%s'/'%s'): open '%s' error: %s",
210             src, dst, src, strerror(errno));
211         return -1;
212     }
213
214     if ((dfd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, mode)) < 0) {
215         LOG(log_error, logtype_afpd, "copy_file('%s'/'%s'): open '%s' error: %s",
216             src, dst, dst, strerror(errno));
217         ret = -1;
218         goto exit;
219     }
220
221     while ((cc = read(sfd, filebuf, sizeof(filebuf)))) {
222         if (cc < 0) {
223             if (errno == EINTR)
224                 continue;
225             LOG(log_error, logtype_afpd, "copy_file('%s'/'%s'): read '%s' error: %s",
226                 src, dst, src, strerror(errno));
227             ret = -1;
228             goto exit;
229         }
230
231         buflen = cc;
232         while (buflen > 0) {
233             if ((cc = write(dfd, filebuf, buflen)) < 0) {
234                 if (errno == EINTR)
235                     continue;
236                 LOG(log_error, logtype_afpd, "copy_file('%s'/'%s'): read '%s' error: %s",
237                     src, dst, dst, strerror(errno));
238                 ret = -1;
239                 goto exit;
240             }
241             buflen -= cc;
242         }
243     }
244
245 exit:
246     if (sfd != -1)
247         close(sfd);
248
249     if (dfd != -1) {
250         int err;
251
252         err = close(dfd);
253         if (!ret && err) {
254             /* don't bother to report an error if there's already one */
255             LOG(log_error, logtype_afpd, "copy_file('%s'/'%s'): close '%s' error: %s",
256                 src, dst, dst, strerror(errno));
257             ret = -1;
258         }
259     }
260
261     return ret;
262 }
263
264 /* 
265  * at wrapper for netatalk_unlink
266  */
267 int netatalk_unlinkat(int dirfd, const char *name)
268 {
269 #ifdef HAVE_RENAMEAT
270     if (dirfd == -1)
271         dirfd = AT_FDCWD;
272
273     if (unlinkat(dirfd, name, 0) < 0) {
274         switch (errno) {
275         case ENOENT :
276             break;
277         case EROFS:
278             return AFPERR_VLOCK;
279         case EPERM:
280         case EACCES :
281             return AFPERR_ACCESS;
282         default :
283             return AFPERR_PARAM;
284         }
285     }
286     return AFP_OK;
287 #else
288     return netatalk_unlink(name);
289 #endif
290
291     /* DEADC0DE */
292     return 0;
293 }
294
295 /*
296  * @brief This is equivalent of unix rename()
297  *
298  * unix_rename mulitplexes rename and renameat. If we dont HAVE_RENAMEAT, sfd and dfd
299  * are ignored.
300  *
301  * @param sfd        (r) if we HAVE_RENAMEAT, -1 gives AT_FDCWD
302  * @param oldpath    (r) guess what
303  * @param dfd        (r) same as sfd
304  * @param newpath    (r) guess what
305  */
306 int unix_rename(int sfd, const char *oldpath, int dfd, const char *newpath)
307 {
308 #ifdef HAVE_RENAMEAT
309     if (sfd == -1)
310         sfd = AT_FDCWD;
311     if (dfd == -1)
312         dfd = AT_FDCWD;
313
314     if (renameat(sfd, oldpath, dfd, newpath) < 0)
315         return -1;        
316 #else
317     if (rename(oldpath, newpath) < 0)
318         return -1;
319 #endif  /* HAVE_RENAMEAT */
320
321     return 0;
322 }
323
324 /* 
325  * @brief stat/fsstatat multiplexer
326  *
327  * statat mulitplexes stat and fstatat. If we dont HAVE_RENAMEAT, dirfd is ignored.
328  *
329  * @param dirfd   (r) Only used if HAVE_RENAMEAT, ignored else, -1 gives AT_FDCWD
330  * @param path    (r) pathname
331  * @param st      (rw) pointer to struct stat
332  */
333 int statat(int dirfd, const char *path, struct stat *st)
334 {
335 #ifdef HAVE_RENAMEAT
336     if (dirfd == -1)
337         dirfd = AT_FDCWD;
338     return (fstatat(dirfd, path, st, 0));
339 #else
340     return (stat(path, st));
341 #endif            
342
343     /* DEADC0DE */
344     return -1;
345 }
346
347 /* 
348  * @brief lstat/fsstatat multiplexer
349  *
350  * lstatat mulitplexes lstat and fstatat. If we dont HAVE_RENAMEAT, dirfd is ignored.
351  *
352  * @param dirfd   (r) Only used if HAVE_RENAMEAT, ignored else, -1 gives AT_FDCWD
353  * @param path    (r) pathname
354  * @param st      (rw) pointer to struct stat
355  */
356 int lstatat(int dirfd, const char *path, struct stat *st)
357 {
358 #ifdef HAVE_RENAMEAT
359     if (dirfd == -1)
360         dirfd = AT_FDCWD;
361     return (fstatat(dirfd, path, st, AT_SYMLINK_NOFOLLOW));
362 #else
363     return (lstat(path, st));
364 #endif            
365
366     /* DEADC0DE */
367     return -1;
368 }
369
370 /* 
371  * @brief opendir wrapper for *at semantics support
372  *
373  * opendirat chdirs to dirfd if dirfd != -1 before calling opendir on path.
374  *
375  * @param dirfd   (r) if != -1, chdir(dirfd) before opendir(path)
376  * @param path    (r) pathname
377  */
378 DIR *opendirat(int dirfd, const char *path)
379 {
380     DIR *ret;
381     int cwd = -1;
382
383     if (dirfd != -1) {
384         if (((cwd = open(".", O_RDONLY)) == -1) || (fchdir(dirfd) != 0)) {
385             ret = NULL;
386             goto exit;
387         }
388     }
389
390     ret = opendir(path);
391
392     if (dirfd != -1 && fchdir(cwd) != 0) {
393         LOG(log_error, logtype_afpd, "opendirat: cant chdir back. exit!");
394         exit(EXITERR_SYS);
395     }
396
397 exit:
398     if (cwd != -1)
399         close(cwd);
400
401     return ret;
402 }