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