]> arthur.barton.de Git - netatalk.git/blobdiff - libatalk/vfs/ea.c
Force conversion of EA names
[netatalk.git] / libatalk / vfs / ea.c
index 6cfa6132d4e00531fec6b9ab432a843dc05c58a2..7b9cb9a7fa46171d4de0ce9ed82e1f6f1a68efeb 100644 (file)
@@ -1,5 +1,5 @@
 /*
-  $Id: ea.c,v 1.3 2009-10-14 15:04:01 franklahm Exp $
+  $Id: ea.c,v 1.14 2009-10-29 19:20:28 franklahm Exp $
   Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
 
   This program is free software; you can redistribute it and/or modify
   GNU General Public License for more details.
 */
 
-/* According to man fsattr.5 we must define _ATFILE_SOURCE */
-#ifdef HAVE_SOLARIS_EAS
-#define _ATFILE_SOURCE
-#endif
-
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif /* HAVE_CONFIG_H */
@@ -39,6 +34,7 @@
 #include <atalk/volume.h>
 #include <atalk/vfs.h>
 #include <atalk/util.h>
+#include <atalk/unix.h>
 
 /*
  * Store Extended Attributes inside .AppleDouble folders as follows:
  * - store EAs in files "fileWithEAs::EA::testEA1" and "fileWithEAs::EA::testEA2"
  */
 
+/* 
+ * Build mode for EA header from file mode
+ */
+static inline mode_t ea_header_mode(mode_t mode)
+{
+    /* Same as ad_hf_mode(mode) */
+    mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);
+    /* Owner must be able to open, read and w-lock it, in order to chmod from eg 0000 -> 0xxxx*/
+    mode |= S_IRUSR | S_IWUSR;
+    return mode;
+}
+
+/* 
+ * Build mode for EA file from file mode
+ */
+static inline mode_t ea_mode(mode_t mode)
+{
+    /* Same as ad_hf_mode(mode) */
+    mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);
+    return mode;
+}
+
+/*
+  Taken form afpd/desktop.c
+*/
+static char *mtoupath(const struct vol *vol, const char *mpath)
+{
+    static char  upath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
+    const char   *m;
+    char         *u;
+    size_t       inplen;
+    size_t       outlen;
+    uint16_t     flags = CONV_ESCAPEHEX | CONV_FORCE;
+
+    if (!mpath)
+        return NULL;
+
+    if ( *mpath == '\0' ) {
+        return( "." );
+    }
+
+    m = mpath;
+    u = upath;
+
+    inplen = strlen(m);
+    outlen = MAXPATHLEN;
+
+    if ((size_t)-1 == (outlen = convert_charset(CH_UTF8_MAC,
+                                                vol->v_volcharset,
+                                                vol->v_maccharset,
+                                                m, inplen, u, outlen, &flags)) ) {
+        return NULL;
+    }
+
+    return( upath );
+}
+
+
 /*
  * Function: unpack_header
  *
@@ -230,7 +284,7 @@ static int pack_header(struct ea * restrict ea)
  *    ea           (r) ea handle
  *    eaname       (r) name of EA or NULL
  *
- * Returns: pointer to name in static buffer
+ * Returns: pointer to name in static buffer, NULL on error
  *
  * Effects:
  *
@@ -246,7 +300,7 @@ static char * ea_path(const struct ea * restrict ea,
     static char pathbuf[MAXPATHLEN + 1];
 
     /* get name of a adouble file from uname */
-    adname = ea->vol->vfs->ad_path(ea->filename, (ea->ea_flags & EA_DIR) ? ADFLAGS_DIR : 0);
+    adname = ea->vol->ad_path(ea->filename, (ea->ea_flags & EA_DIR) ? ADFLAGS_DIR : 0);
     /* copy it so we can work with it */
     strlcpy(pathbuf, adname, MAXPATHLEN + 1);
     /* append "::EA" */
@@ -254,6 +308,8 @@ static char * ea_path(const struct ea * restrict ea,
 
     if (eaname) {
         strlcat(pathbuf, "::", MAXPATHLEN + 1);
+        if ((eaname = mtoupath(ea->vol, eaname)) == NULL)
+            return NULL;
         strlcat(pathbuf, eaname, MAXPATHLEN + 1);
     }
 
@@ -476,8 +532,12 @@ static int write_ea(const struct ea * restrict ea,
     struct stat st;
     char *eaname;
 
-    eaname = ea_path(ea, attruname);
-    LOG(log_maxdebug, logtype_afpd, "write_ea: ea_apth: %s", eaname);
+    if ((eaname = ea_path(ea, attruname)) == NULL) {
+        LOG(log_error, logtype_afpd, "write_ea('%s'): ea_path error", attruname);
+        return AFPERR_MISC;
+    }
+
+    LOG(log_maxdebug, logtype_afpd, "write_ea('%s')", eaname);
 
     /* Check if it exists, remove if yes*/
     if ((stat(eaname, &st)) == 0) {
@@ -502,7 +562,7 @@ static int write_ea(const struct ea * restrict ea,
     }
 
     if ((write(fd, ibuf, attrsize)) != attrsize) {
-        LOG(log_error, logtype_afpd, "write_ea: short write: %s", eaname);
+        LOG(log_error, logtype_afpd, "write_ea('%s'): write: %s", eaname, strerror(errno));
         ret = -1;
         goto exit;
     }
@@ -532,7 +592,10 @@ static int delete_ea_file(const struct ea * restrict ea,
     char *eafile;
     struct stat st;
 
-    eafile = ea_path(ea, eaname);
+    if ((eafile = ea_path(ea, eaname)) == NULL) {
+        LOG(log_error, logtype_afpd, "delete_ea_file('%s'): ea_path error", eaname);
+        return -1;
+    }
 
     /* Check if it exists, remove if yes*/
     if ((stat(eafile, &st)) == 0) {
@@ -590,9 +653,9 @@ static int ea_open(const struct vol * restrict vol,
     memset(ea, 0, sizeof(struct ea));
 
     ea->vol = vol;              /* ea_close needs it */
-
     ea->ea_flags = eaflags;
-    if (S_ISDIR(st.st_mode))
+    /* Dont care for errors, eg when removing the file is already gone */
+    if (!stat(uname, &st) && S_ISDIR(st.st_mode))
         ea->ea_flags |=  EA_DIR;
 
     if ( ! (ea->filename = strdup(uname))) {
@@ -653,7 +716,7 @@ static int ea_open(const struct vol * restrict vol,
 
     /* Now lock, open and read header file from disk */
     if ((ea->ea_fd = open(eaname, (ea->ea_flags & EA_RDWR) ? O_RDWR : O_RDONLY)) == -1) {
-        LOG(log_error, logtype_afpd, "ea_open: error on open for header: %s", eaname);
+        LOG(log_error, logtype_afpd, "ea_open('%s'): error: %s", eaname, strerror(errno));
         ret = -1;
         goto exit;
     }
@@ -839,12 +902,7 @@ exit:
  *
  * Copies EA size into rbuf in network order. Increments *rbuflen +4.
  */
-int get_easize(const struct vol * restrict vol,
-               char * restrict rbuf,
-               int * restrict rbuflen,
-               const char * restrict uname,
-               int oflag,
-               const char * restrict attruname)
+int get_easize(VFS_FUNC_ARGS_EA_GETSIZE)
 {
     int ret = AFPERR_MISC, count = 0;
     uint32_t uint32;
@@ -900,18 +958,13 @@ int get_easize(const struct vol * restrict vol,
  *
  * Copies EA into rbuf. Increments *rbuflen accordingly.
  */
-int get_eacontent(const struct vol * restrict vol,
-                  char * restrict rbuf,
-                  int * restrict rbuflen,
-                  const char * restrict uname,
-                  int oflag,
-                  const char * restrict attruname,
-                  int maxreply)
+int get_eacontent(VFS_FUNC_ARGS_EA_GETCONTENT)
 {
     int ret = AFPERR_MISC, count = 0, fd = -1;
     uint32_t uint32;
     size_t toread;
     struct ea ea;
+    char *eafile;
 
     LOG(log_debug, logtype_afpd, "get_eacontent('%s/%s')", uname, attruname);
 
@@ -922,7 +975,12 @@ int get_eacontent(const struct vol * restrict vol,
 
     while (count < ea.ea_count) {
         if (strcmp(attruname, (*ea.ea_entries)[count].ea_name) == 0) {
-            if ((fd = open(ea_path(&ea, attruname), O_RDONLY)) == -1) {
+            if ( (eafile = ea_path(&ea, attruname)) == NULL) {
+                ret = AFPERR_MISC;
+                break;
+            }
+
+            if ((fd = open(eafile, O_RDONLY)) == -1) {
                 ret = AFPERR_MISC;
                 break;
             }
@@ -983,11 +1041,7 @@ int get_eacontent(const struct vol * restrict vol,
  * Copies names of all EAs of uname as consecutive C strings into rbuf.
  * Increments *buflen accordingly.
  */
-int list_eas(const struct vol * restrict vol,
-             char * restrict attrnamebuf,
-             int * restrict buflen,
-             const char * restrict uname,
-             int oflag)
+int list_eas(VFS_FUNC_ARGS_EA_LIST)
 {
     int count = 0, attrbuflen = *buflen, ret = AFP_OK, len;
     char *buf = attrnamebuf;
@@ -1067,12 +1121,7 @@ exit:
  * Copies names of all EAs of uname as consecutive C strings into rbuf.
  * Increments *rbuflen accordingly.
  */
-int set_ea(const struct vol * restrict vol,
-           const char * restrict uname,
-           const char * restrict attruname,
-           const char * restrict ibuf,
-           size_t attrsize,
-           int oflag)
+int set_ea(VFS_FUNC_ARGS_EA_SET)
 {
     int ret = AFP_OK;
     struct ea ea;
@@ -1124,10 +1173,7 @@ exit:
  *
  * Removes EA attruname from file uname.
  */
-int remove_ea(const struct vol * restrict vol,
-              const char * restrict uname,
-              const char * restrict attruname,
-              int oflag)
+int remove_ea(VFS_FUNC_ARGS_EA_REMOVE)
 {
     int ret = AFP_OK;
     struct ea ea;
@@ -1161,374 +1207,6 @@ exit:
     return ret;
 }
 
-/**********************************************************************************
- * Solaris EA VFS funcs
- **********************************************************************************/
-
-/*
- * Function: sol_get_easize
- *
- * Purpose: get size of an EA on Solaris native EA
- *
- * Arguments:
- *
- *    vol          (r) current volume
- *    rbuf         (w) DSI reply buffer
- *    rbuflen      (rw) current length of data in reply buffer
- *    uname        (r) filename
- *    oflag        (r) link and create flag
- *    attruname    (r) name of attribute
- *
- * Returns: AFP code: AFP_OK on success or appropiate AFP error code
- *
- * Effects:
- *
- * Copies EA size into rbuf in network order. Increments *rbuflen +4.
- */
-#ifdef HAVE_SOLARIS_EAS
-int sol_get_easize(const struct vol * restrict vol,
-                   char * restrict rbuf,
-                   int * restrict rbuflen,
-                   const char * restrict uname,
-                   int oflag,
-                   cons char * restrict attruname)
-{
-    int                 ret, attrdirfd;
-    uint32_t            attrsize;
-    struct stat         st;
-
-    LOG(log_debug7, logtype_afpd, "sol_getextattr_size(%s): attribute: \"%s\"", uname, attruname);
-
-    if ( -1 == (attrdirfd = attropen(uname, ".", O_RDONLY | oflag))) {
-        if (errno == ELOOP) {
-            /* its a symlink and client requested O_NOFOLLOW  */
-            LOG(log_debug, logtype_afpd, "sol_getextattr_size(%s): encountered symlink with kXAttrNoFollow", uname);
-
-            memset(rbuf, 0, 4);
-            *rbuflen += 4;
-
-            return AFP_OK;
-        }
-        LOG(log_error, logtype_afpd, "sol_getextattr_size: attropen error: %s", strerror(errno));
-        return AFPERR_MISC;
-    }
-
-    if ( -1 == (fstatat(attrdirfd, attruname, &st, 0))) {
-        LOG(log_error, logtype_afpd, "sol_getextattr_size: fstatat error: %s", strerror(errno));
-        ret = AFPERR_MISC;
-        goto exit;
-    }
-    attrsize = (st.st_size > MAX_EA_SIZE) ? MAX_EA_SIZE : st.st_size;
-
-    /* Start building reply packet */
-
-    LOG(log_debug7, logtype_afpd, "sol_getextattr_size(%s): attribute: \"%s\", size: %u", uname, attruname, attrsize);
-
-    /* length of attribute data */
-    attrsize = htonl(attrsize);
-    memcpy(rbuf, &attrsize, 4);
-    *rbuflen += 4;
-
-    ret = AFP_OK;
-
-exit:
-    close(attrdirfd);
-    return ret;
-}
-#endif /* HAVE_SOLARIS_EAS */
-
-/*
- * Function: sol_get_eacontent
- *
- * Purpose: copy Solaris native EA into rbuf
- *
- * Arguments:
- *
- *    vol          (r) current volume
- *    rbuf         (w) DSI reply buffer
- *    rbuflen      (rw) current length of data in reply buffer
- *    uname        (r) filename
- *    oflag        (r) link and create flag
- *    attruname    (r) name of attribute
- *    maxreply     (r) maximum EA size as of current specs/real-life
- *
- * Returns: AFP code: AFP_OK on success or appropiate AFP error code
- *
- * Effects:
- *
- * Copies EA into rbuf. Increments *rbuflen accordingly.
- */
-#ifdef HAVE_SOLARIS_EAS
-int sol_get_eacontent(const struct vol * restrict vol,
-                      char * restrict rbuf,
-                      int * restrict rbuflen,
-                      const char * restrict uname,
-                      int oflag,
-                      char * restrict attruname,
-                      int maxreply)
-{
-    int                 ret, attrdirfd;
-    size_t              toread, okread = 0, len;
-    char                *datalength;
-    struct stat         st;
-
-    if ( -1 == (attrdirfd = attropen(uname, attruname, O_RDONLY | oflag))) {
-        if (errno == ELOOP) {
-            /* its a symlink and client requested O_NOFOLLOW  */
-            LOG(log_debug, logtype_afpd, "sol_getextattr_content(%s): encountered symlink with kXAttrNoFollow", uname);
-
-            memset(rbuf, 0, 4);
-            *rbuflen += 4;
-
-            return AFP_OK;
-        }
-        LOG(log_error, logtype_afpd, "sol_getextattr_content(%s): attropen error: %s", attruname, strerror(errno));
-        return AFPERR_MISC;
-    }
-
-    if ( -1 == (fstat(attrdirfd, &st))) {
-        LOG(log_error, logtype_afpd, "sol_getextattr_content(%s): fstatat error: %s", attruname,strerror(errno));
-        ret = AFPERR_MISC;
-        goto exit;
-    }
-
-    /* Start building reply packet */
-
-    maxreply -= MAX_REPLY_EXTRA_BYTES;
-    if (maxreply > MAX_EA_SIZE)
-        maxreply = MAX_EA_SIZE;
-
-    /* But never send more than the client requested */
-    toread = (maxreply < st.st_size) ? maxreply : st.st_size;
-
-    LOG(log_debug7, logtype_afpd, "sol_getextattr_content(%s): attribute: \"%s\", size: %u", uname, attruname, maxreply);
-
-    /* remember where we must store length of attribute data in rbuf */
-    datalength = rbuf;
-    rbuf += 4;
-    *rbuflen += 4;
-
-    while (1) {
-        len = read(attrdirfd, rbuf, toread);
-        if (len == -1) {
-            LOG(log_error, logtype_afpd, "sol_getextattr_content(%s): read error: %s", attruname, strerror(errno));
-            ret = AFPERR_MISC;
-            goto exit;
-        }
-        okread += len;
-        rbuf += len;
-        *rbuflen += len;
-        if ((len == 0) || (okread == toread))
-            break;
-    }
-
-    okread = htonl((uint32_t)okread);
-    memcpy(datalength, &okread, 4);
-
-    ret = AFP_OK;
-
-exit:
-    close(attrdirfd);
-    return ret;
-}
-#endif /* HAVE_SOLARIS_EAS */
-
-/*
- * Function: sol_list_eas
- *
- * Purpose: copy names of Solaris native EA into attrnamebuf
- *
- * Arguments:
- *
- *    vol          (r) current volume
- *    attrnamebuf  (w) store names a consecutive C strings here
- *    buflen       (rw) length of names in attrnamebuf
- *    uname        (r) filename
- *    oflag        (r) link and create flag
- *
- * Returns: AFP code: AFP_OK on success or appropiate AFP error code
- *
- * Effects:
- *
- * Copies names of all EAs of uname as consecutive C strings into rbuf.
- * Increments *rbuflen accordingly.
- */
-#ifdef HAVE_SOLARIS_EAS
-int sol_list_eas(const struct vol * restrict vol,
-                 char * restrict attrnamebuf,
-                 int * restrict buflen,
-                 const char * restrict uname,
-                 int oflag)
-{
-    int ret, attrbuflen = *buflen, len, attrdirfd = 0;
-    struct dirent *dp;
-    DIR *dirp = NULL;
-
-    /* Now list file attribute dir */
-    if ( -1 == (attrdirfd = attropen( uname, ".", O_RDONLY | oflag))) {
-        if (errno == ELOOP) {
-            /* its a symlink and client requested O_NOFOLLOW */
-            ret = AFPERR_BADTYPE;
-            goto exit;
-        }
-        LOG(log_error, logtype_afpd, "sol_list_extattr(%s): error opening atttribute dir: %s", uname, strerror(errno));
-        ret = AFPERR_MISC;
-        goto exit;
-    }
-
-    if (NULL == (dirp = fdopendir(attrdirfd))) {
-        LOG(log_error, logtype_afpd, "sol_list_extattr(%s): error opening atttribute dir: %s", uname, strerror(errno));
-        ret = AFPERR_MISC;
-        goto exit;
-    }
-
-    while ((dp = readdir(dirp)))  {
-        /* check if its "." or ".." */
-        if ((strcmp(dp->d_name, ".") == 0) || (strcmp(dp->d_name, "..") == 0) ||
-            (strcmp(dp->d_name, "SUNWattr_ro") == 0) || (strcmp(dp->d_name, "SUNWattr_rw") == 0))
-            continue;
-
-        len = strlen(dp->d_name);
-
-        /* Convert name to CH_UTF8_MAC and directly store in in the reply buffer */
-        if ( 0 >= ( len = convert_string(vol->v_volcharset, CH_UTF8_MAC, dp->d_name, len, attrnamebuf + attrbuflen, 255)) ) {
-            ret = AFPERR_MISC;
-            goto exit;
-        }
-        if (len == 255)
-            /* convert_string didn't 0-terminate */
-            attrnamebuf[attrbuflen + 255] = 0;
-
-        LOG(log_debug7, logtype_afpd, "sol_list_extattr(%s): attribute: %s", uname, dp->d_name);
-
-        attrbuflen += len + 1;
-        if (attrbuflen > (ATTRNAMEBUFSIZ - 256)) {
-            /* Next EA name could overflow, so bail out with error.
-               FIXME: evantually malloc/memcpy/realloc whatever.
-               Is it worth it ? */
-            LOG(log_warning, logtype_afpd, "sol_list_extattr(%s): running out of buffer for EA names", uname);
-            ret = AFPERR_MISC;
-            goto exit;
-        }
-    }
-
-    ret = AFP_OK;
-
-exit:
-    if (dirp)
-        closedir(dirp);
-
-    if (attrdirfd > 0)
-        close(attrdirfd);
-
-    *buflen = attrbuflen;
-    return ret;
-}
-#endif /* HAVE_SOLARIS_EAS */
-
-/*
- * Function: sol_set_ea
- *
- * Purpose: set a Solaris native EA
- *
- * Arguments:
- *
- *    vol          (r) current volume
- *    uname        (r) filename
- *    attruname    (r) EA name
- *    ibuf         (r) buffer with EA content
- *    attrsize     (r) length EA in ibuf
- *    oflag        (r) link and create flag
- *
- * Returns: AFP code: AFP_OK on success or appropiate AFP error code
- *
- * Effects:
- *
- * Copies names of all EAs of uname as consecutive C strings into rbuf.
- * Increments *rbuflen accordingly.
- */
-#ifdef HAVE_SOLARIS_EAS
-int sol_set_ea(const struct vol * restrict vol,
-               const char * restrict u_name,
-               const char * restrict attruname,
-               const char * restrict ibuf,
-               size_t attrsize,
-               int oflag)
-{
-    int attrdirfd;
-
-    if ( -1 == (attrdirfd = attropen(u_name, attruname, oflag, 0666))) {
-        if (errno == ELOOP) {
-            /* its a symlink and client requested O_NOFOLLOW  */
-            LOG(log_debug, logtype_afpd, "afp_setextattr(%s): encountered symlink with kXAttrNoFollow", s_path->u_name);
-            return AFP_OK;
-        }
-        LOG(log_error, logtype_afpd, "afp_setextattr(%s): attropen error: %s", s_path->u_name, strerror(errno));
-        return AFPERR_MISC;
-    }
-
-    if ((write(attrdirfd, ibuf, attrsize)) != attrsize) {
-        LOG(log_error, logtype_afpd, "afp_setextattr(%s): read error: %s", attruname, strerror(errno));
-        return AFPERR_MISC;
-    }
-
-    return AFP_OK;
-}
-#endif /* HAVE_SOLARIS_EAS */
-
-/*
- * Function: sol_remove_ea
- *
- * Purpose: remove a Solaris native EA
- *
- * Arguments:
- *
- *    vol          (r) current volume
- *    uname        (r) filename
- *    attruname    (r) EA name
- *    oflag        (r) link and create flag
- *
- * Returns: AFP code: AFP_OK on success or appropiate AFP error code
- *
- * Effects:
- *
- * Removes EA attruname from file uname.
- */
-#ifdef HAVE_SOLARIS_EAS
-int sol_remove_ea(const struct vol * restrict vol,
-                  const char * restrict uname,
-                  const char * restrict attruname,
-                  int oflag)
-{
-    int attrdirfd;
-
-    if ( -1 == (attrdirfd = attropen(uname, ".", oflag))) {
-        switch (errno) {
-        case ELOOP:
-            /* its a symlink and client requested O_NOFOLLOW  */
-            LOG(log_debug, logtype_afpd, "afp_remextattr(%s): encountered symlink with kXAttrNoFollow", s_path->u_name);
-            return AFP_OK;
-        case EACCES:
-            LOG(log_debug, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
-            return AFPERR_ACCESS;
-        default:
-            LOG(log_error, logtype_afpd, "afp_remextattr(%s): attropen error: %s", s_path->u_name, strerror(errno));
-            return AFPERR_MISC;
-        }
-    }
-
-    if ( -1 == (unlinkat(attrdirfd, attruname, 0)) ) {
-        if (errno == EACCES) {
-            LOG(log_debug, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
-            return AFPERR_ACCESS;
-        }
-        LOG(log_error, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
-        return AFPERR_MISC;
-    }
-
-}
-#endif /* HAVE_SOLARIS_EAS */
-
 /******************************************************************************************
  * EA VFS funcs that deal with file/dir cp/mv/rm
  ******************************************************************************************/
@@ -1620,9 +1298,15 @@ int ea_renamefile(VFS_FUNC_ARGS_RENAMEFILE)
         easize = (*srcea.ea_entries)[count].ea_size;
 
         /* Build src and dst paths for rename() */
-        eapath = ea_path(&srcea, eaname);
+        if ((eapath = ea_path(&srcea, eaname)) == NULL) {
+            ret = AFPERR_MISC;
+            goto exit;
+        }
         strcpy(srceapath, eapath);
-        eapath = ea_path(&dstea, eaname);
+        if ((eapath = ea_path(&dstea, eaname)) == NULL) {
+            ret = AFPERR_MISC;
+            goto exit;
+        }
 
         LOG(log_maxdebug, logtype_afpd, "ea_renamefile('%s/%s'): moving EA '%s' to '%s'",
             src, dst, srceapath, eapath);
@@ -1661,3 +1345,306 @@ exit:
     ea_close(&dstea);
        return ret;
 }
+
+int ea_copyfile(VFS_FUNC_ARGS_COPYFILE)
+{
+    int    count = 0;
+    int    ret = AFP_OK;
+    size_t easize;
+    char   srceapath[ MAXPATHLEN + 1];
+    char   *eapath;
+    char   *eaname;
+    struct ea srcea;
+    struct ea dstea;
+    struct adouble ad;
+
+    LOG(log_debug, logtype_afpd, "ea_copyfile('%s'/'%s')", src, dst);
+
+    /* Open EA stuff */
+    if ((ea_open(vol, src, EA_RDWR, &srcea)) != 0) {
+        if (errno == ENOENT)
+            /* no EA files, nothing to do */
+            return AFP_OK;
+        else {
+            LOG(log_error, logtype_afpd, "ea_copyfile('%s'/'%s'): ea_open error: '%s'", src, dst, src);
+            return AFPERR_MISC;
+        }
+    }
+
+    if ((ea_open(vol, dst, EA_RDWR | EA_CREATE, &dstea)) != 0) {
+        if (errno == ENOENT) {
+            /* Possibly the .AppleDouble folder didn't exist, we create it and try again */
+            ad_init(&ad, vol->v_adouble, vol->v_ad_options); 
+            if ((ad_open(dst, ADFLAGS_HF, O_RDWR | O_CREAT, 0666, &ad)) != 0) {
+                LOG(log_error, logtype_afpd, "ea_copyfile('%s/%s'): ad_open error: '%s'", src, dst, dst);
+                ret = AFPERR_MISC;
+                goto exit;
+            }
+            ad_close(&ad, ADFLAGS_HF);
+            if ((ea_open(vol, dst, EA_RDWR | EA_CREATE, &dstea)) != 0) {
+                ret = AFPERR_MISC;
+                goto exit;
+            }
+        }
+    }
+
+    /* Loop through all EAs: */
+    while (count < srcea.ea_count) {
+        /* Copy EA */
+        eaname = (*srcea.ea_entries)[count].ea_name;
+        easize = (*srcea.ea_entries)[count].ea_size;
+
+        /* Build src and dst paths for copy_file() */
+        if ((eapath = ea_path(&srcea, eaname)) == NULL) {
+            ret = AFPERR_MISC;
+            goto exit;
+        }
+        strcpy(srceapath, eapath);
+        if ((eapath = ea_path(&dstea, eaname)) == NULL) {
+            ret = AFPERR_MISC;
+            goto exit;
+        }
+
+        LOG(log_maxdebug, logtype_afpd, "ea_copyfile('%s/%s'): copying EA '%s' to '%s'",
+            src, dst, srceapath, eapath);
+
+        /* Add EA to dstea */
+        if ((ea_addentry(&dstea, eaname, easize, 0)) == -1) {
+            LOG(log_error, logtype_afpd, "ea_copyfile('%s/%s'): ea_addentry('%s') error",
+                src, dst, eaname);
+            ret = AFPERR_MISC;
+            goto exit;
+        }
+
+        /* Now copy the EA */
+        if ((copy_file( srceapath, eapath, (0666 & ~vol->v_umask))) < 0) {
+            LOG(log_error, logtype_afpd, "ea_copyfile('%s/%s'): copying EA '%s' to '%s'",
+                src, dst, srceapath, eapath);
+            ret = AFPERR_MISC;
+            goto exit;
+        }
+
+        count++;
+    }
+
+exit:
+    ea_close(&srcea);
+    ea_close(&dstea);
+       return ret;
+}
+
+int ea_chown(VFS_FUNC_ARGS_CHOWN)
+{
+    LOG(log_debug, logtype_afpd, "ea_chown('%s')", path);
+
+    int count = 0, ret = AFP_OK;
+    char *eaname;
+    struct ea ea;
+
+    /* Open EA stuff */
+    if ((ea_open(vol, path, EA_RDWR, &ea)) != 0) {
+        if (errno == ENOENT)
+            /* no EA files, nothing to do */
+            return AFP_OK;
+        else {
+            LOG(log_error, logtype_afpd, "ea_chown('%s'): error calling ea_open", path);
+            return AFPERR_MISC;
+        }
+    }
+
+    if ((chown(ea_path(&ea, NULL), uid, gid)) != 0) {
+        switch (errno) {
+        case EPERM:
+        case EACCES:
+            ret = AFPERR_ACCESS;
+            goto exit;
+        default:
+            ret = AFPERR_MISC;
+            goto exit;
+        }
+    }
+
+    while (count < ea.ea_count) {
+        if ((eaname = ea_path(&ea, (*ea.ea_entries)[count].ea_name)) == NULL) {
+            ret = AFPERR_MISC;
+            goto exit;
+        }
+        if ((chown(eaname, uid, gid)) != 0) {
+            switch (errno) {
+            case EPERM:
+            case EACCES:
+                ret = AFPERR_ACCESS;
+                goto exit;
+            default:
+                ret = AFPERR_MISC;
+                goto exit;
+            }
+            continue;
+        }
+
+        count++;
+    }
+
+exit:
+    if ((ea_close(&ea)) != 0) {
+        LOG(log_error, logtype_afpd, "ea_chown('%s'): error closing ea handle", path);
+        return AFPERR_MISC;
+    }
+
+    return ret;
+}
+
+int ea_chmod_file(VFS_FUNC_ARGS_SETFILEMODE)
+{
+    LOG(log_debug, logtype_afpd, "ea_chmod_file('%s')", name);
+
+    int count = 0, ret = AFP_OK;
+    const char *eaname;
+    struct ea ea;
+
+    /* Open EA stuff */
+    if ((ea_open(vol, name, EA_RDWR, &ea)) != 0) {
+        if (errno == ENOENT)
+            /* no EA files, nothing to do */
+            return AFP_OK;
+        else
+            return AFPERR_MISC;
+    }
+
+    /* Set mode on EA header file */
+    if ((setfilmode(ea_path(&ea, NULL), ea_header_mode(mode), NULL, vol->v_umask)) != 0) {
+        LOG(log_error, logtype_afpd, "ea_chmod_file('%s'): %s", ea_path(&ea, NULL), strerror(errno));
+        switch (errno) {
+        case EPERM:
+        case EACCES:
+            ret = AFPERR_ACCESS;
+            goto exit;
+        default:
+            ret = AFPERR_MISC;
+            goto exit;
+        }
+    }
+
+    /* Set mode on EA files */
+    while (count < ea.ea_count) {
+        if ((eaname = ea_path(&ea, (*ea.ea_entries)[count].ea_name)) == NULL) {
+            ret = AFPERR_MISC;
+            goto exit;
+        }
+        if ((setfilmode(eaname, ea_mode(mode), NULL, vol->v_umask)) != 0) {
+            LOG(log_error, logtype_afpd, "ea_chmod_file('%s'): %s", eaname, strerror(errno));
+            switch (errno) {
+            case EPERM:
+            case EACCES:
+                ret = AFPERR_ACCESS;
+                goto exit;
+            default:
+                ret = AFPERR_MISC;
+                goto exit;
+            }
+            continue;
+        }
+
+        count++;
+    }
+
+exit:
+    if ((ea_close(&ea)) != 0) {
+        LOG(log_error, logtype_afpd, "ea_chmod_file('%s'): error closing ea handle", name);
+        return AFPERR_MISC;
+    }
+
+    return ret;
+}
+
+int ea_chmod_dir(VFS_FUNC_ARGS_SETDIRUNIXMODE)
+{
+    LOG(log_debug, logtype_afpd, "ea_chmod_dir('%s')", name);
+
+    int ret = AFP_OK;
+    uid_t uid;
+    const char *eaname;
+    const char *eaname_safe = NULL;
+    struct ea ea;
+
+    /* .AppleDouble already might be inaccesible, so we must run as id 0 */
+    uid = geteuid();
+    if (seteuid(0)) {
+        LOG(log_error, logtype_afpd, "ea_chmod_dir('%s'): seteuid: %s", name, strerror(errno));
+        return AFPERR_MISC;
+    }
+
+    /* Open EA stuff */
+    if ((ea_open(vol, name, EA_RDWR, &ea)) != 0) {
+        /* ENOENT --> no EA files, nothing to do */
+        if (errno != ENOENT)
+            ret = AFPERR_MISC;
+        if (seteuid(uid) < 0) {
+            LOG(log_error, logtype_afpd, "can't seteuid back: %s", strerror(errno));
+            exit(EXITERR_SYS);
+        }
+        return ret;
+    }
+
+    /* Set mode on EA header */
+    if ((setfilmode(ea_path(&ea, NULL), ea_header_mode(mode), NULL, vol->v_umask)) != 0) {
+        LOG(log_error, logtype_afpd, "ea_chmod_dir('%s'): %s", ea_path(&ea, NULL), strerror(errno));
+        switch (errno) {
+        case EPERM:
+        case EACCES:
+            ret = AFPERR_ACCESS;
+            goto exit;
+        default:
+            ret = AFPERR_MISC;
+            goto exit;
+        }
+    }
+
+    /* Set mode on EA files */
+    int count = 0;
+    while (count < ea.ea_count) {
+        eaname = (*ea.ea_entries)[count].ea_name;
+        /*
+         * Be careful with EA names from the EA header!
+         * Eg NFS users might have access to them, can inject paths using ../ or /.....
+         * FIXME:
+         * Until the EA code escapes / in EA name requests from the client, these therefor wont work.
+         */
+        if ((eaname_safe = strrchr(eaname, '/'))) {
+            LOG(log_warning, logtype_afpd, "ea_chmod_dir('%s'): contains a slash", eaname);
+            eaname = eaname_safe;
+        }
+        if ((eaname = ea_path(&ea, eaname)) == NULL) {
+            ret = AFPERR_MISC;
+            goto exit;
+        }
+        if ((setfilmode(eaname, ea_mode(mode), NULL, vol->v_umask)) != 0) {
+            LOG(log_error, logtype_afpd, "ea_chmod_dir('%s'): %s", eaname, strerror(errno));
+            switch (errno) {
+            case EPERM:
+            case EACCES:
+                ret = AFPERR_ACCESS;
+                goto exit;
+            default:
+                ret = AFPERR_MISC;
+                goto exit;
+            }
+            continue;
+        }
+
+        count++;
+    }
+
+exit:
+    if (seteuid(uid) < 0) {
+        LOG(log_error, logtype_afpd, "can't seteuid back: %s", strerror(errno));
+        exit(EXITERR_SYS);
+    }
+
+    if ((ea_close(&ea)) != 0) {
+        LOG(log_error, logtype_afpd, "ea_chmod_dir('%s'): error closing ea handle", name);
+        return AFPERR_MISC;
+    }
+
+    return ret;
+}