]> arthur.barton.de Git - netatalk.git/blob - libatalk/vfs/unix.c
Opening files without metadata EA may result in broken metadata EA
[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 #include <atalk/compat.h>
26 #include <atalk/errchk.h>
27 #include <atalk/ea.h>
28
29 /* ------------------------- */
30 int dir_rx_set(mode_t mode)
31 {
32     return (mode & (S_IXUSR | S_IRUSR)) == (S_IXUSR | S_IRUSR);
33 }
34
35 /* --------------------- */
36 int setfilmode(const struct vol *vol, const char *name, mode_t mode, struct stat *st)
37 {
38     struct stat sb;
39     mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO;  /* rwx for owner group and other, by default */
40
41     if (!st) {
42         if (lstat(name, &sb) != 0)
43             return -1;
44         st = &sb;
45     }
46
47     mode |= st->st_mode & ~mask; /* keep other bits from previous mode */
48
49     if (ochmod((char *)name, mode & ~vol->v_umask, st, vol_syml_opt(vol) | O_NETATALK_ACL) < 0 && errno != EPERM ) {
50         return -1;
51     }
52     return 0;
53 }
54
55 /*
56  * @brief system rmdir with afp error code.
57  *
58  * Supports *at semantics (cf openat) if HAVE_ATFUNCS. Pass dirfd=-1 to ignore this.
59  */
60 int netatalk_rmdir_all_errors(int dirfd, const char *name)
61 {
62     int err;
63
64 #ifdef HAVE_ATFUNCS
65     if (dirfd == -1)
66         dirfd = AT_FDCWD;
67     err = unlinkat(dirfd, name, AT_REMOVEDIR);
68 #else
69     err = rmdir(name);
70 #endif
71
72     if (err < 0) {
73         switch ( errno ) {
74         case ENOENT :
75             return AFPERR_NOOBJ;
76         case ENOTEMPTY :
77             return AFPERR_DIRNEMPT;
78         case EPERM:
79         case EACCES :
80             return AFPERR_ACCESS;
81         case EROFS:
82             return AFPERR_VLOCK;
83         default :
84             return AFPERR_PARAM;
85         }
86     }
87     return AFP_OK;
88 }
89
90 /*
91  * @brief System rmdir with afp error code, but ENOENT is not an error.
92  *
93  * Supports *at semantics (cf openat) if HAVE_ATFUNCS. Pass dirfd=-1 to ignore this.
94  */
95 int netatalk_rmdir(int dirfd, const char *name)
96 {
97     int ret = netatalk_rmdir_all_errors(dirfd, name);
98     if (ret == AFPERR_NOOBJ)
99         return AFP_OK;
100     return ret;
101 }
102
103 /* -------------------
104    system unlink with afp error code.
105    ENOENT is not an error.
106 */
107 int netatalk_unlink(const char *name)
108 {
109     if (unlink(name) < 0) {
110         switch (errno) {
111         case ENOENT :
112             break;
113         case EROFS:
114             return AFPERR_VLOCK;
115         case EPERM:
116         case EACCES :
117             return AFPERR_ACCESS;
118         default :
119             return AFPERR_PARAM;
120         }
121     }
122     return AFP_OK;
123 }
124
125 /**************************************************************************
126  * *at semnatics support functions (like openat, renameat standard funcs)
127  **************************************************************************/
128
129 /* Copy all file data from one file fd to another */
130 int copy_file_fd(int sfd, int dfd)
131 {
132     EC_INIT;
133     ssize_t cc;
134     size_t  buflen;
135     char   filebuf[NETATALK_DIOSZ_STACK];
136
137     while ((cc = read(sfd, filebuf, sizeof(filebuf)))) {
138         if (cc < 0) {
139             if (errno == EINTR)
140                 continue;
141             LOG(log_error, logtype_afpd, "copy_file_fd: %s", strerror(errno));
142             EC_FAIL;
143         }
144
145         buflen = cc;
146         while (buflen > 0) {
147             if ((cc = write(dfd, filebuf, buflen)) < 0) {
148                 if (errno == EINTR)
149                     continue;
150                 LOG(log_error, logtype_afpd, "copy_file_fd: %s", strerror(errno));
151                 EC_FAIL;
152             }
153             buflen -= cc;
154         }
155     }
156
157 EC_CLEANUP:
158     EC_EXIT;
159 }
160
161 /* 
162  * Supports *at semantics if HAVE_ATFUNCS, pass dirfd=-1 to ignore this
163  */
164 int copy_file(int dirfd, const char *src, const char *dst, mode_t mode)
165 {
166     int    ret = 0;
167     int    sfd = -1;
168     int    dfd = -1;
169
170 #ifdef HAVE_ATFUNCS
171     if (dirfd == -1)
172         dirfd = AT_FDCWD;
173     sfd = openat(dirfd, src, O_RDONLY);
174 #else
175     sfd = open(src, O_RDONLY);
176 #endif
177     if (sfd < 0) {
178         LOG(log_info, logtype_afpd, "copy_file('%s'/'%s'): open '%s' error: %s",
179             src, dst, src, strerror(errno));
180         return -1;
181     }
182
183     if ((dfd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, mode)) < 0) {
184         LOG(log_info, logtype_afpd, "copy_file('%s'/'%s'): open '%s' error: %s",
185             src, dst, dst, strerror(errno));
186         ret = -1;
187         goto exit;
188     }
189
190     ret = copy_file_fd(sfd, dfd);
191
192 exit:
193     if (sfd != -1)
194         close(sfd);
195
196     if (dfd != -1) {
197         int err;
198
199         err = close(dfd);
200         if (!ret && err) {
201             /* don't bother to report an error if there's already one */
202             LOG(log_error, logtype_afpd, "copy_file('%s'/'%s'): close '%s' error: %s",
203                 src, dst, dst, strerror(errno));
204             ret = -1;
205         }
206     }
207
208     return ret;
209 }
210
211 /*!
212  * Copy an EA from one file to another
213  *
214  * Supports *at semantics if HAVE_ATFUNCS, pass dirfd=-1 to ignore this
215  */
216 int copy_ea(const char *ea, int dirfd, const char *src, const char *dst, mode_t mode)
217 {
218     EC_INIT;
219     int    sfd = -1;
220     int    dfd = -1;
221     size_t easize;
222     char   *eabuf = NULL;
223
224 #ifdef HAVE_ATFUNCS
225     if (dirfd == -1)
226         dirfd = AT_FDCWD;
227     EC_NEG1_LOG( sfd = openat(dirfd, src, O_RDONLY) );
228 #else
229     EC_NEG1_LOG( sfd = open(src, O_RDONLY) );
230 #endif
231     EC_NEG1_LOG( dfd = open(dst, O_WRONLY, mode) );
232
233     if ((easize = sys_fgetxattr(sfd, ea, NULL, 0)) > 0) {
234         EC_NULL_LOG( eabuf = malloc(easize));
235         EC_NEG1_LOG( easize = sys_fgetxattr(sfd, ea, eabuf, easize) );
236         EC_NEG1_LOG( easize = sys_fsetxattr(dfd, ea, eabuf, easize, 0) );
237     }
238
239 EC_CLEANUP:
240     if (sfd != -1)
241         close(sfd);
242     if (dfd != -1)
243         close(dfd);
244     free(eabuf);
245     EC_EXIT;
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 }