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