]> arthur.barton.de Git - netatalk.git/blobdiff - libatalk/vfs/vfs.c
Convert afp_moveandrename and all called funcs to XXXat semantics if available
[netatalk.git] / libatalk / vfs / vfs.c
index 0dc4df4e1ed9cdeff46123669313740ac1a52377..dadedc5bbee20c21ac384b0ee7d19f435656c069 100644 (file)
@@ -1,5 +1,6 @@
 /*
     Copyright (c) 2004 Didier Gautheron
+    Copyright (c) 2009 Frank Lahm
  
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 
 #include <string.h>
 #include <stdio.h>
+#include <stdlib.h>
 
 #include <atalk/afp.h>    
 #include <atalk/adouble.h>
-#include <atalk/vfs.h>
+#include <atalk/ea.h>
+#include <atalk/acl.h>
 #include <atalk/logger.h>
 #include <atalk/util.h>
 #include <atalk/volume.h>
+#include <atalk/vfs.h>
 #include <atalk/directory.h>
-
-#ifdef HAVE_NFSv4_ACLS
-extern int remove_acl(const char *name);
-#endif
+#include <atalk/unix.h>
 
 struct perm {
     uid_t uid;
@@ -86,25 +87,29 @@ for_each_adouble(const char *from, const char *name, rf_loop fn, void *data, int
 
 static int netatalk_name(const char *name)
 {
-    return strcasecmp(name,".AppleDB") &&
-        strcasecmp(name,".AppleDouble") &&
+    return strcasecmp(name,".AppleDouble") &&
+        strcasecmp(name,".AppleDB") &&
         strcasecmp(name,".AppleDesktop");
 }
 
-static int validupath_adouble(const struct vol *vol, const char *name) 
+static int validupath_adouble(VFS_FUNC_ARGS_VALIDUPATH)
 {
-    return (vol->v_flags & AFPVOL_USEDOTS) ? 
-        netatalk_name(name) && strcasecmp(name,".Parent"): name[0] != '.';
+    if (name[0] != '.')
+        return 1;
+    
+    if (!(vol->v_flags & AFPVOL_USEDOTS))
+        return 0;
+        
+    return netatalk_name(name) && strcasecmp(name,".Parent");
 }                                           
 
 /* ----------------- */
-static int RF_chown_adouble(const struct vol *vol, const char *path, uid_t uid, gid_t gid)
-
+static int RF_chown_adouble(VFS_FUNC_ARGS_CHOWN)
 {
     struct stat st;
     char        *ad_p;
 
-    ad_p = vol->vfs->ad_path(path, ADFLAGS_HF );
+    ad_p = vol->ad_path(path, ADFLAGS_HF );
 
     if ( stat( ad_p, &st ) < 0 )
         return 0; /* ignore */
@@ -113,7 +118,7 @@ static int RF_chown_adouble(const struct vol *vol, const char *path, uid_t uid,
 }
 
 /* ----------------- */
-static int RF_renamedir_adouble(const struct vol *vol _U_, const char *oldpath _U_, const char *newpath _U_)
+static int RF_renamedir_adouble(VFS_FUNC_ARGS_RENAMEDIR)
 {
     return 0;
 }
@@ -136,7 +141,7 @@ static int deletecurdir_adouble_loop(struct dirent *de, char *name, void *data _
     return 0;
 }
 
-static int RF_deletecurdir_adouble(const struct vol *vol)
+static int RF_deletecurdir_adouble(VFS_FUNC_ARGS_DELETECURDIR)
 {
     int err;
 
@@ -144,7 +149,7 @@ static int RF_deletecurdir_adouble(const struct vol *vol)
        as well. */
     if ((err = for_each_adouble("deletecurdir", ".AppleDouble", deletecurdir_adouble_loop, NULL, 1, vol->v_umask))) 
         return err;
-    return netatalk_rmdir( ".AppleDouble" );
+    return netatalk_rmdir(-1, ".AppleDouble" );
 }
 
 /* ----------------- */
@@ -153,15 +158,15 @@ static int adouble_setfilmode(const char * name, mode_t mode, struct stat *st, m
     return setfilmode(name, ad_hf_mode(mode), st, v_umask);
 }
 
-static int RF_setfilmode_adouble(const struct vol *vol, const char * name, mode_t mode, struct stat *st)
+static int RF_setfilmode_adouble(VFS_FUNC_ARGS_SETFILEMODE)
 {
-    return adouble_setfilmode(vol->vfs->ad_path( name, ADFLAGS_HF ), mode, st, vol->v_umask);
+    return adouble_setfilmode(vol->ad_path(name, ADFLAGS_HF ), mode, st, vol->v_umask);
 }
 
 /* ----------------- */
-static int RF_setdirunixmode_adouble(const struct vol *vol, const char * name, mode_t mode, struct stat *st)
+static int RF_setdirunixmode_adouble(VFS_FUNC_ARGS_SETDIRUNIXMODE)
 {
-    char *adouble = vol->vfs->ad_path( name, ADFLAGS_DIR );
+    char *adouble = vol->ad_path(name, ADFLAGS_DIR );
     int  dropbox = vol->v_flags;
 
     if (dir_rx_set(mode)) {
@@ -169,7 +174,7 @@ static int RF_setdirunixmode_adouble(const struct vol *vol, const char * name, m
             return -1;
     }
 
-    if (adouble_setfilmode(vol->vfs->ad_path( name, ADFLAGS_DIR ), mode, st, vol->v_umask) < 0) 
+    if (adouble_setfilmode(vol->ad_path(name, ADFLAGS_DIR ), mode, st, vol->v_umask) < 0) 
         return -1;
 
     if (!dir_rx_set(mode)) {
@@ -198,11 +203,11 @@ static int setdirmode_adouble_loop(struct dirent *de _U_, char *name, void *data
     return 0;
 }
 
-static int RF_setdirmode_adouble(const struct vol *vol, const char * name, mode_t mode, struct stat *st _U_)
+static int RF_setdirmode_adouble(VFS_FUNC_ARGS_SETDIRMODE)
 {
     int   dropbox = vol->v_flags;
     mode_t hf_mode = ad_hf_mode(mode);
-    char  *adouble = vol->vfs->ad_path( name, ADFLAGS_DIR );
+    char  *adouble = vol->ad_path(name, ADFLAGS_DIR );
     char  *adouble_p = ad_dir(adouble);
 
     if (dir_rx_set(mode)) {
@@ -233,8 +238,7 @@ static int setdirowner_adouble_loop(struct dirent *de _U_, char *name, void *dat
     return 0;
 }
 
-static int RF_setdirowner_adouble(const struct vol *vol, const char *name, uid_t uid, gid_t gid)
-
+static int RF_setdirowner_adouble(VFS_FUNC_ARGS_SETDIROWNER)
 {
     int           noadouble = vol_noadouble(vol);
     char          *adouble_p;
@@ -244,7 +248,7 @@ static int RF_setdirowner_adouble(const struct vol *vol, const char *name, uid_t
     owner.uid = uid;
     owner.gid = gid;
 
-    adouble_p = ad_dir(vol->vfs->ad_path( name, ADFLAGS_DIR ));
+    adouble_p = ad_dir(vol->ad_path(name, ADFLAGS_DIR ));
 
     if (for_each_adouble("setdirowner", adouble_p, setdirowner_adouble_loop, &owner, noadouble, vol->v_umask)) 
         return -1;
@@ -267,28 +271,28 @@ static int RF_setdirowner_adouble(const struct vol *vol, const char *name, uid_t
 }
 
 /* ----------------- */
-static int RF_deletefile_adouble(const struct vol *vol, const char *file )
+static int RF_deletefile_adouble(VFS_FUNC_ARGS_DELETEFILE)
 {
-       return netatalk_unlink(vol->vfs->ad_path( file, ADFLAGS_HF));
+       return netatalk_unlinkat(dirfd, vol->ad_path(file, ADFLAGS_HF));
 }
 
 /* ----------------- */
-static int RF_renamefile_adouble(const struct vol *vol, const char *src, const char *dst)
+static int RF_renamefile_adouble(VFS_FUNC_ARGS_RENAMEFILE)
 {
     char  adsrc[ MAXPATHLEN + 1];
     int   err = 0;
 
-    strcpy( adsrc, vol->vfs->ad_path( src, 0 ));
-    if (unix_rename( adsrc, vol->vfs->ad_path( dst, 0 )) < 0) {
+    strcpy( adsrc, vol->ad_path(src, 0 ));
+    if (unix_rename(dirfd, adsrc, -1, vol->ad_path(dst, 0 )) < 0) {
         struct stat st;
 
         err = errno;
         if (errno == ENOENT) {
                struct adouble    ad;
 
-            if (stat(adsrc, &st)) /* source has no ressource fork, */
+            if (lstatat(dirfd, adsrc, &st)) /* source has no ressource fork, */
                 return 0;
-            
+
             /* We are here  because :
              * -there's no dest folder. 
              * -there's no .AppleDouble in the dest folder.
@@ -299,7 +303,7 @@ static int RF_renamefile_adouble(const struct vol *vol, const char *src, const c
             ad_init(&ad, vol->v_adouble, vol->v_ad_options); 
             if (!ad_open(dst, ADFLAGS_HF, O_RDWR | O_CREAT, 0666, &ad)) {
                ad_close(&ad, ADFLAGS_HF);
-               if (!unix_rename( adsrc, vol->vfs->ad_path( dst, 0 )) ) 
+               if (!unix_rename(dirfd, adsrc, -1, vol->ad_path(dst, 0 )) ) 
                    err = 0;
                 else 
                    err = errno;
@@ -317,47 +321,51 @@ static int RF_renamefile_adouble(const struct vol *vol, const char *src, const c
 }
 
 #ifdef HAVE_NFSv4_ACLS
-static int RF_acl(const struct vol *vol, const char *path, int cmd, int count, ace_t *aces)
+static int RF_solaris_acl(VFS_FUNC_ARGS_ACL)
 {
     static char buf[ MAXPATHLEN + 1];
     struct stat st;
+    int len;
 
     if ((stat(path, &st)) != 0)
        return -1;
     if (S_ISDIR(st.st_mode)) {
-       if ((snprintf(buf, MAXPATHLEN, "%s/.AppleDouble",path)) < 0)
+       len = snprintf(buf, MAXPATHLEN, "%s/.AppleDouble",path);
+       if (len < 0 || len >=  MAXPATHLEN)
            return -1;
        /* set acl on .AppleDouble dir first */
        if ((acl(buf, cmd, count, aces)) != 0)
            return -1;
        /* now set ACL on ressource fork */
-       if ((acl(vol->vfs->ad_path(path, ADFLAGS_DIR), cmd, count, aces)) != 0)
+       if ((acl(vol->ad_path(path, ADFLAGS_DIR), cmd, count, aces)) != 0)
            return -1;
     } else
        /* set ACL on ressource fork */
-       if ((acl(vol->vfs->ad_path(path, ADFLAGS_HF), cmd, count, aces)) != 0)
+       if ((acl(vol->ad_path(path, ADFLAGS_HF), cmd, count, aces)) != 0)
            return -1;
 
     return 0;
 }
 
-static int RF_remove_acl(const struct vol *vol, const char *path, int dir)
+static int RF_solaris_remove_acl(VFS_FUNC_ARGS_REMOVE_ACL)
 {
     int ret;
     static char buf[ MAXPATHLEN + 1];
+    int len;
 
     if (dir) {
-       if ((snprintf(buf, MAXPATHLEN, "%s/.AppleDouble",path)) < 0)
+       len = snprintf(buf, MAXPATHLEN, "%s/.AppleDouble",path);
+       if (len < 0 || len >=  MAXPATHLEN)
            return AFPERR_MISC;
        /* remove ACL from .AppleDouble/.Parent first */
-       if ((ret = remove_acl(vol->vfs->ad_path(path, ADFLAGS_DIR))) != AFP_OK)
+       if ((ret = remove_acl(vol->ad_path(path, ADFLAGS_DIR))) != AFP_OK)
            return ret;
        /* now remove from .AppleDouble dir */
        if ((ret = remove_acl(buf)) != AFP_OK)
            return ret;
     } else
        /* remove ACL from ressource fork */
-       if ((ret = remove_acl(vol->vfs->ad_path(path, ADFLAGS_HF))) != AFP_OK)
+       if ((ret = remove_acl(vol->ad_path(path, ADFLAGS_HF))) != AFP_OK)
            return ret;
 
     return AFP_OK;
@@ -377,8 +385,7 @@ static int ads_chown_loop(struct dirent *de _U_, char *name, void *data, int fla
     return 0;
 }
 
-static int RF_chown_ads(const struct vol *vol, const char *path, uid_t uid, gid_t gid)
-
+static int RF_chown_ads(VFS_FUNC_ARGS_CHOWN)
 {
     struct        stat st;
     char          *ad_p;
@@ -388,7 +395,7 @@ static int RF_chown_ads(const struct vol *vol, const char *path, uid_t uid, gid_
     owner.gid = gid;
 
 
-    ad_p = ad_dir(vol->vfs->ad_path(path, ADFLAGS_HF ));
+    ad_p = ad_dir(vol->ad_path(path, ADFLAGS_HF ));
 
     if ( stat( ad_p, &st ) < 0 ) {
        /* ignore */
@@ -407,7 +414,7 @@ static int deletecurdir_ads1_loop(struct dirent *de _U_, char *name, void *data
     return netatalk_unlink(name);
 }
 
-static int ads_delete_rf(char *name) 
+static int ads_delete_rf(char *name)
 {
     int err;
 
@@ -420,7 +427,7 @@ static int ads_delete_rf(char *name)
     */
     if ((err = for_each_adouble("deletecurdir", name, deletecurdir_ads1_loop, NULL, 1, 0))) 
         return err;
-    return netatalk_rmdir(name);
+    return netatalk_rmdir(-1, name);
 }
 
 static int deletecurdir_ads_loop(struct dirent *de, char *name, void *data _U_, int flag _U_, mode_t v_umask _U_)
@@ -436,14 +443,15 @@ static int deletecurdir_ads_loop(struct dirent *de, char *name, void *data _U_,
     return ads_delete_rf(name);
 }
 
-static int RF_deletecurdir_ads(const struct vol *vol _U_)
+static int RF_deletecurdir_ads(VFS_FUNC_ARGS_DELETECURDIR)
 {
     int err;
-    
+
     /* delete stray .AppleDouble files. this happens to get .Parent files as well. */
     if ((err = for_each_adouble("deletecurdir", ".AppleDouble", deletecurdir_ads_loop, NULL, 1, 0))) 
         return err;
-    return netatalk_rmdir( ".AppleDouble" );
+
+    return netatalk_rmdir(-1, ".AppleDouble" );
 }
 
 /* ------------------- */
@@ -491,15 +499,15 @@ static int ads_setfilmode(const char * name, mode_t mode, struct stat *st, mode_
     return 0;
 }
 
-static int RF_setfilmode_ads(const struct vol *vol, const char * name, mode_t mode, struct stat *st)
+static int RF_setfilmode_ads(VFS_FUNC_ARGS_SETFILEMODE)
 {
-    return ads_setfilmode(ad_dir(vol->vfs->ad_path( name, ADFLAGS_HF )), mode, st, vol->v_umask);
+    return ads_setfilmode(ad_dir(vol->ad_path(name, ADFLAGS_HF )), mode, st, vol->v_umask);
 }
 
 /* ------------------- */
-static int RF_setdirunixmode_ads(const struct vol *vol, const char * name, mode_t mode, struct stat *st)
+static int RF_setdirunixmode_ads(VFS_FUNC_ARGS_SETDIRUNIXMODE)
 {
-    char *adouble = vol->vfs->ad_path( name, ADFLAGS_DIR );
+    char *adouble = vol->ad_path(name, ADFLAGS_DIR );
     char   ad_p[ MAXPATHLEN + 1];
     int dropbox = vol->v_flags;
 
@@ -516,7 +524,7 @@ static int RF_setdirunixmode_ads(const struct vol *vol, const char * name, mode_
             return -1;
     }
 
-    if (ads_setfilmode(ad_dir(vol->vfs->ad_path( name, ADFLAGS_DIR)), mode, st, vol->v_umask) < 0)
+    if (ads_setfilmode(ad_dir(vol->ad_path(name, ADFLAGS_DIR)), mode, st, vol->v_umask) < 0)
         return -1;
 
     if (!dir_rx_set(mode)) {
@@ -562,9 +570,9 @@ static int setdirmode_ads_loop(struct dirent *de _U_, char *name, void *data, in
     return 0;
 }
 
-static int RF_setdirmode_ads(const struct vol *vol, const char * name, mode_t mode, struct stat *st _U_)
+static int RF_setdirmode_ads(VFS_FUNC_ARGS_SETDIRMODE)
 {
-    char *adouble = vol->vfs->ad_path( name, ADFLAGS_DIR );
+    char *adouble = vol->ad_path(name, ADFLAGS_DIR );
     char   ad_p[ MAXPATHLEN + 1];
     struct dir_mode param;
 
@@ -617,7 +625,7 @@ static int setdirowner_ads_loop(struct dirent *de _U_, char *name, void *data, i
     return 0;
 }
 
-static int RF_setdirowner_ads(const struct vol *vol, const char *name, uid_t uid, gid_t gid)
+static int RF_setdirowner_ads(VFS_FUNC_ARGS_SETDIROWNER)
 {
     int           noadouble = vol_noadouble(vol);
     char          adouble_p[ MAXPATHLEN + 1];
@@ -627,7 +635,7 @@ static int RF_setdirowner_ads(const struct vol *vol, const char *name, uid_t uid
     owner.uid = uid;
     owner.gid = gid;
 
-    strlcpy(adouble_p, ad_dir(vol->vfs->ad_path( name, ADFLAGS_DIR )), sizeof(adouble_p));
+    strlcpy(adouble_p, ad_dir(vol->ad_path(name, ADFLAGS_DIR )), sizeof(adouble_p));
 
     if (for_each_adouble("setdirowner", ad_dir(adouble_p), setdirowner_ads_loop, &owner, noadouble, 0)) 
         return -1;
@@ -650,28 +658,50 @@ static int RF_setdirowner_ads(const struct vol *vol, const char *name, uid_t uid
 }
 
 /* ------------------- */
-static int RF_deletefile_ads(const struct vol *vol, const char *file )
+static int RF_deletefile_ads(VFS_FUNC_ARGS_DELETEFILE)
 {
-    char *ad_p = ad_dir(vol->vfs->ad_path(file, ADFLAGS_HF ));
+    int ret = 0;
+    int cwd = -1;
+    char *ad_p;
+
+    ad_p = ad_dir(vol->ad_path(file, ADFLAGS_HF ));
+
+    if (dirfd != -1) {
+        if (((cwd = open(".", O_RDONLY)) == -1) || (fchdir(dirfd) != 0)) {
+            ret = AFPERR_MISC;
+            goto exit;
+        }
+    }
+
+    ret = ads_delete_rf(ad_p);
 
-    return ads_delete_rf(ad_p);
+    if (dirfd != -1 && fchdir(cwd) != 0) {
+        LOG(log_error, logtype_afpd, "RF_deletefile_ads: cant chdir back. exit!");
+        exit(EXITERR_SYS);
+    }
+
+exit:
+    if (cwd != -1)
+        close(cwd);
+
+    return ret;
 }
 
 /* --------------------------- */
-static int RF_renamefile_ads(const struct vol *vol, const char *src, const char *dst)
+static int RF_renamefile_ads(VFS_FUNC_ARGS_RENAMEFILE)
 {
     char  adsrc[ MAXPATHLEN + 1];
     int   err = 0;
 
-    strcpy( adsrc, ad_dir(vol->vfs->ad_path( src, 0 )));
-    if (unix_rename( adsrc, ad_dir(vol->vfs->ad_path( dst, 0 ))) < 0) {
+    strcpy( adsrc, ad_dir(vol->ad_path(src, 0 )));
+    if (unix_rename(dirfd, adsrc, -1, ad_dir(vol->ad_path(dst, 0 ))) < 0) {
         struct stat st;
 
         err = errno;
         if (errno == ENOENT) {
                struct adouble    ad;
 
-            if (stat(adsrc, &st)) /* source has no ressource fork, */
+            if (lstatat(dirfd, adsrc, &st)) /* source has no ressource fork, */
                 return 0;
             
             /* We are here  because :
@@ -686,8 +716,8 @@ static int RF_renamefile_ads(const struct vol *vol, const char *src, const char
                ad_close(&ad, ADFLAGS_HF);
 
                /* We must delete it */
-               RF_deletefile_ads(vol, dst );
-               if (!unix_rename( adsrc, ad_dir(vol->vfs->ad_path( dst, 0 ))) ) 
+               RF_deletefile_ads(vol, -1, dst );
+               if (!unix_rename(dirfd, adsrc, -1, ad_dir(vol->ad_path(dst, 0 ))) ) 
                    err = 0;
                 else 
                    err = errno;
@@ -707,59 +737,57 @@ static int RF_renamefile_ads(const struct vol *vol, const char *src, const char
 /*************************************************************************
  * osx adouble format 
  ************************************************************************/
-static int validupath_osx(const struct vol *vol, const char *name) 
+static int validupath_osx(VFS_FUNC_ARGS_VALIDUPATH)
 {
     return strncmp(name,"._", 2) && (
       (vol->v_flags & AFPVOL_USEDOTS) ? netatalk_name(name): name[0] != '.');
 }             
 
 /* ---------------- */
-static int RF_renamedir_osx(const struct vol *vol, const char *oldpath, const char *newpath)
+static int RF_renamedir_osx(VFS_FUNC_ARGS_RENAMEDIR)
 {
     /* We simply move the corresponding ad file as well */
     char   tempbuf[258]="._";
-    return rename(vol->vfs->ad_path(oldpath,0),strcat(tempbuf,newpath));
+    return unix_rename(dirfd, vol->ad_path(oldpath,0), -1, strcat(tempbuf,newpath));
 }
 
 /* ---------------- */
-static int RF_deletecurdir_osx(const struct vol *vol)
+static int RF_deletecurdir_osx(VFS_FUNC_ARGS_DELETECURDIR)
 {
-    return netatalk_unlink( vol->vfs->ad_path(".",0) );
+    return netatalk_unlink( vol->ad_path(".",0) );
 }
 
 /* ---------------- */
-static int RF_setdirunixmode_osx(const struct vol *vol, const char * name, mode_t mode, struct stat *st)
+static int RF_setdirunixmode_osx(VFS_FUNC_ARGS_SETDIRUNIXMODE)
 {
-    return adouble_setfilmode(vol->vfs->ad_path( name, ADFLAGS_DIR ), mode, st, vol->v_umask);
+    return adouble_setfilmode(vol->ad_path(name, ADFLAGS_DIR ), mode, st, vol->v_umask);
 }
 
 /* ---------------- */
-static int 
-RF_setdirmode_osx(const struct vol *vol _U_, const char *name _U_, mode_t mode _U_, struct stat *st _U_)
+static int RF_setdirmode_osx(VFS_FUNC_ARGS_SETDIRMODE)
 {
     return 0;
 }
 
 /* ---------------- */
-static int 
-RF_setdirowner_osx(const struct vol *vol _U_, const char *path _U_, uid_t uid _U_, gid_t gid _U_)
+static int RF_setdirowner_osx(VFS_FUNC_ARGS_SETDIROWNER)
 {
        return 0;
 }
 
 /* ---------------- */
-static int RF_renamefile_osx(const struct vol *vol, const char *src, const char *dst)
+static int RF_renamefile_osx(VFS_FUNC_ARGS_RENAMEFILE)
 {
     char  adsrc[ MAXPATHLEN + 1];
     int   err = 0;
 
-    strcpy( adsrc, vol->vfs->ad_path( src, 0 ));
+    strcpy( adsrc, vol->ad_path(src, 0 ));
 
-    if (unix_rename( adsrc, vol->vfs->ad_path( dst, 0 )) < 0) {
+    if (unix_rename(dirfd, adsrc, -1, vol->ad_path(dst, 0 )) < 0) {
         struct stat st;
 
         err = errno;
-        if (errno == ENOENT && stat(adsrc, &st)) /* source has no ressource fork, */
+        if (errno == ENOENT && lstatat(dirfd, adsrc, &st)) /* source has no ressource fork, */
             return 0;
         errno = err;
         return -1;
@@ -767,92 +795,239 @@ static int RF_renamefile_osx(const struct vol *vol, const char *src, const char
     return 0;
 }
 
+/********************************************************************************************
+ * VFS chaining
+ ********************************************************************************************/
+
+/* 
+ * Up until we really start stacking many VFS modules on top of one another or use
+ * dynamic module loading like we do for UAMs, up until then we just stack VFS modules
+ * via an fixed size array.
+ * All VFS funcs must return AFP_ERR codes. When a func in the chain returns an error
+ * this error code will be returned to the caller, BUT the chain in followed and all
+ * following funcs are called in order to give them a chance.
+ */
+
+/* 
+ * Define most VFS funcs with macros as they all do the same.
+ * Only "ad_path" and "validupath" will NOT do stacking and only
+ * call the func from the first module.
+ */
+
+#define VFS_MFUNC(name, args, vars) \
+    static int vfs_ ## name(args) \
+    { \
+        int i = 0, ret = AFP_OK, err; \
+        while (vol->vfs_modules[i]) { \
+            if (vol->vfs_modules[i]->vfs_ ## name) { \
+                err = vol->vfs_modules[i]->vfs_ ## name (vars); \
+                if ((ret == AFP_OK) && (err != AFP_OK)) \
+                    ret = err; \
+            } \
+            i ++; \
+        } \
+        return ret; \
+    }
+
+VFS_MFUNC(chown, VFS_FUNC_ARGS_CHOWN, VFS_FUNC_VARS_CHOWN)
+VFS_MFUNC(renamedir, VFS_FUNC_ARGS_RENAMEDIR, VFS_FUNC_VARS_RENAMEDIR) 
+VFS_MFUNC(deletecurdir, VFS_FUNC_ARGS_DELETECURDIR, VFS_FUNC_VARS_DELETECURDIR)
+VFS_MFUNC(setfilmode, VFS_FUNC_ARGS_SETFILEMODE, VFS_FUNC_VARS_SETFILEMODE)
+VFS_MFUNC(setdirmode, VFS_FUNC_ARGS_SETDIRMODE, VFS_FUNC_VARS_SETDIRMODE)
+VFS_MFUNC(setdirunixmode, VFS_FUNC_ARGS_SETDIRUNIXMODE, VFS_FUNC_VARS_SETDIRUNIXMODE)
+VFS_MFUNC(setdirowner, VFS_FUNC_ARGS_SETDIROWNER, VFS_FUNC_VARS_SETDIROWNER)
+VFS_MFUNC(deletefile, VFS_FUNC_ARGS_DELETEFILE, VFS_FUNC_VARS_DELETEFILE)
+VFS_MFUNC(renamefile, VFS_FUNC_ARGS_RENAMEFILE, VFS_FUNC_VARS_RENAMEFILE)
+VFS_MFUNC(copyfile, VFS_FUNC_ARGS_COPYFILE, VFS_FUNC_VARS_COPYFILE)
+VFS_MFUNC(acl, VFS_FUNC_ARGS_ACL, VFS_FUNC_VARS_ACL)
+VFS_MFUNC(remove_acl, VFS_FUNC_ARGS_REMOVE_ACL, VFS_FUNC_VARS_REMOVE_ACL)
+VFS_MFUNC(ea_getsize, VFS_FUNC_ARGS_EA_GETSIZE, VFS_FUNC_VARS_EA_GETSIZE)
+VFS_MFUNC(ea_getcontent, VFS_FUNC_ARGS_EA_GETCONTENT, VFS_FUNC_VARS_EA_GETCONTENT)
+VFS_MFUNC(ea_list, VFS_FUNC_ARGS_EA_LIST, VFS_FUNC_VARS_EA_LIST)
+VFS_MFUNC(ea_set, VFS_FUNC_ARGS_EA_SET, VFS_FUNC_VARS_EA_SET)
+VFS_MFUNC(ea_remove, VFS_FUNC_ARGS_EA_REMOVE, VFS_FUNC_VARS_EA_REMOVE)
+
+static int vfs_validupath(VFS_FUNC_ARGS_VALIDUPATH)
+{
+    return vol->vfs_modules[0]->vfs_validupath(VFS_FUNC_VARS_VALIDUPATH);
+}
+
+/*
+ * These function pointers get called from the lib users via vol->vfs->func.
+ * These funcs are defined via the macros above.
+ */
+static struct vfs_ops vfs_master_funcs = {
+    vfs_validupath,
+    vfs_chown,
+    vfs_renamedir,
+    vfs_deletecurdir,
+    vfs_setfilmode,
+    vfs_setdirmode,
+    vfs_setdirunixmode,
+    vfs_setdirowner,
+    vfs_deletefile,
+    vfs_renamefile,
+    vfs_copyfile,
+    vfs_acl,
+    vfs_remove_acl,
+    vfs_ea_getsize,
+    vfs_ea_getcontent,
+    vfs_ea_list,
+    vfs_ea_set,
+    vfs_ea_remove
+};
+
+/* 
+ * Primary adouble modules: default, osx, sfm
+ */
+
 static struct vfs_ops netatalk_adouble = {
-    /* ad_path:           */ ad_path,
-    /* validupath:        */ validupath_adouble,
-    /* rf_chown:          */ RF_chown_adouble,
-    /* rf_renamedir:      */ RF_renamedir_adouble,
-    /* rf_deletecurdir:   */ RF_deletecurdir_adouble,
-    /* rf_setfilmode:     */ RF_setfilmode_adouble,
-    /* rf_setdirmode:     */ RF_setdirmode_adouble,
-    /* rf_setdirunixmode: */ RF_setdirunixmode_adouble,
-    /* rf_setdirowner:    */ RF_setdirowner_adouble,
-    /* rf_deletefile:     */ RF_deletefile_adouble,
-    /* rf_renamefile:     */ RF_renamefile_adouble,
-#ifdef HAVE_NFSv4_ACLS
-    /* rf_acl:            */ RF_acl,
-    /* rf_remove_acl      */ RF_remove_acl
-#endif
+    /* vfs_validupath:    */ validupath_adouble,
+    /* vfs_chown:         */ RF_chown_adouble,
+    /* vfs_renamedir:     */ RF_renamedir_adouble,
+    /* vfs_deletecurdir:  */ RF_deletecurdir_adouble,
+    /* vfs_setfilmode:    */ RF_setfilmode_adouble,
+    /* vfs_setdirmode:    */ RF_setdirmode_adouble,
+    /* vfs_setdirunixmode:*/ RF_setdirunixmode_adouble,
+    /* vfs_setdirowner:   */ RF_setdirowner_adouble,
+    /* vfs_deletefile:    */ RF_deletefile_adouble,
+    /* vfs_renamefile:    */ RF_renamefile_adouble,
+    /* vfs_copyfile:      */ NULL,
+    NULL
 };
 
 static struct vfs_ops netatalk_adouble_osx = {
-    /* ad_path:          */ ad_path_osx,
-    /* validupath:       */ validupath_osx,
-    /* rf_chown:         */ RF_chown_adouble,
-    /* rf_renamedir:     */ RF_renamedir_osx,
-    /* rf_deletecurdir:  */ RF_deletecurdir_osx,
-    /* rf_setfilmode:    */ RF_setfilmode_adouble,
-    /* rf_setdirmode:    */ RF_setdirmode_osx,
-    /* rf_setdirunixmode:*/ RF_setdirunixmode_osx,
-    /* rf_setdirowner:   */ RF_setdirowner_osx,
-    /* rf_deletefile:    */ RF_deletefile_adouble,
-    /* rf_renamefile:    */ RF_renamefile_osx,
+    /* vfs_validupath:    */ validupath_osx,
+    /* vfs_chown:         */ RF_chown_adouble,
+    /* vfs_renamedir:     */ RF_renamedir_osx,
+    /* vfs_deletecurdir:  */ RF_deletecurdir_osx,
+    /* vfs_setfilmode:    */ RF_setfilmode_adouble,
+    /* vfs_setdirmode:    */ RF_setdirmode_osx,
+    /* vfs_setdirunixmode:*/ RF_setdirunixmode_osx,
+    /* vfs_setdirowner:   */ RF_setdirowner_osx,
+    /* vfs_deletefile:    */ RF_deletefile_adouble,
+    /* vfs_renamefile:    */ RF_renamefile_osx,
+    /* vfs_copyfile:      */ NULL,
+    NULL
 };
 
 /* samba sfm format. ad_path shouldn't be set her */
 static struct vfs_ops netatalk_adouble_sfm = {
-    /* ad_path:          */ ad_path_sfm,
-    /* validupath:       */ validupath_adouble,
-    /* rf_chown:         */ RF_chown_ads,
-    /* rf_renamedir:     */ RF_renamedir_adouble,
-    /* rf_deletecurdir:  */ RF_deletecurdir_ads,
-    /* rf_setfilmode:    */ RF_setfilmode_ads,
-    /* rf_setdirmode:    */ RF_setdirmode_ads,
-    /* rf_setdirunixmode:*/ RF_setdirunixmode_ads,
-    /* rf_setdirowner:   */ RF_setdirowner_ads,
-    /* rf_deletefile:    */ RF_deletefile_ads,
-    /* rf_renamefile:    */ RF_renamefile_ads,
+    /* vfs_validupath:    */ validupath_adouble,
+    /* vfs_chown:         */ RF_chown_ads,
+    /* vfs_renamedir:     */ RF_renamedir_adouble,
+    /* vfs_deletecurdir:  */ RF_deletecurdir_ads,
+    /* vfs_setfilmode:    */ RF_setfilmode_ads,
+    /* vfs_setdirmode:    */ RF_setdirmode_ads,
+    /* vfs_setdirunixmode:*/ RF_setdirunixmode_ads,
+    /* vfs_setdirowner:   */ RF_setdirowner_ads,
+    /* vfs_deletefile:    */ RF_deletefile_ads,
+    /* vfs_renamefile:    */ RF_renamefile_ads,
+    /* vfs_copyfile:      */ NULL,
+    NULL
 };
 
+/* 
+ * Secondary vfs modules for Extended Attributes
+ */
+
+static struct vfs_ops netatalk_ea_adouble = {
+    /* vfs_validupath:    */ NULL,
+    /* vfs_chown:         */ ea_chown,
+    /* vfs_renamedir:     */ NULL, /* ok */
+    /* vfs_deletecurdir:  */ NULL, /* ok */
+    /* vfs_setfilmode:    */ ea_chmod_file,
+    /* vfs_setdirmode:    */ NULL, /* ok */
+    /* vfs_setdirunixmode:*/ ea_chmod_dir,
+    /* vfs_setdirowner:   */ NULL, /* ok */
+    /* vfs_deletefile:    */ ea_deletefile,
+    /* vfs_renamefile:    */ ea_renamefile,
+    /* vfs_copyfile       */ ea_copyfile,
+    /* vfs_acl:           */ NULL,
+    /* vfs_remove_acl     */ NULL,
+    /* vfs_getsize        */ get_easize,
+    /* vfs_getcontent     */ get_eacontent,
+    /* vfs_list           */ list_eas,
+    /* vfs_set            */ set_ea,
+    /* vfs_remove         */ remove_ea
+};
+
+static struct vfs_ops netatalk_ea_sys = {
+    /* validupath:        */ NULL,
+    /* rf_chown:          */ NULL,
+    /* rf_renamedir:      */ NULL,
+    /* rf_deletecurdir:   */ NULL,
+    /* rf_setfilmode:     */ NULL,
+    /* rf_setdirmode:     */ NULL,
+    /* rf_setdirunixmode: */ NULL,
+    /* rf_setdirowner:    */ NULL,
+    /* rf_deletefile:     */ NULL,
+    /* rf_renamefile:     */ NULL,
+    /* vfs_copyfile:      */ sys_ea_copyfile,
+    /* rf_acl:            */ NULL,
+    /* rf_remove_acl      */ NULL,
+    /* ea_getsize         */ sys_get_easize,
+    /* ea_getcontent      */ sys_get_eacontent,
+    /* ea_list            */ sys_list_eas,
+    /* ea_set             */ sys_set_ea,
+    /* ea_remove          */ sys_remove_ea
+};
+
+/* 
+ * Tertiary VFS modules for ACLs
+ */
+
+#ifdef HAVE_NFSv4_ACLS
+static struct vfs_ops netatalk_solaris_acl_adouble = {
+    /* validupath:        */ NULL,
+    /* rf_chown:          */ NULL,
+    /* rf_renamedir:      */ NULL,
+    /* rf_deletecurdir:   */ NULL,
+    /* rf_setfilmode:     */ NULL,
+    /* rf_setdirmode:     */ NULL,
+    /* rf_setdirunixmode: */ NULL,
+    /* rf_setdirowner:    */ NULL,
+    /* rf_deletefile:     */ NULL,
+    /* rf_renamefile:     */ NULL,
+    /* vfs_copyfile       */ NULL,
+    /* rf_acl:            */ RF_solaris_acl,
+    /* rf_remove_acl      */ RF_solaris_remove_acl,
+    NULL
+};
+#endif
+
 /* ---------------- */
 void initvol_vfs(struct vol *vol)
 {
-    /* adouble stuff */
+    vol->vfs = &vfs_master_funcs;
+
+    /* Default adouble stuff */
     if (vol->v_adouble == AD_VERSION2_OSX) {
-        vol->vfs = &netatalk_adouble_osx;
+        vol->vfs_modules[0] = &netatalk_adouble_osx;
+        vol->ad_path = ad_path_osx;
     }
     else if (vol->v_adouble == AD_VERSION1_SFM) {
-        vol->vfs = &netatalk_adouble_sfm;
+        vol->vfs_modules[0] = &netatalk_adouble_sfm;
+        vol->ad_path = ad_path_sfm;
     }
     else {
-        vol->vfs = &netatalk_adouble;
+        vol->vfs_modules[0] = &netatalk_adouble;
+        vol->ad_path = ad_path;
     }
 
     /* Extended Attributes */
-    if (vol->v_vfs_ea == AFPVOL_EA_SOLARIS) {
-
-#ifdef HAVE_SOLARIS_EAS
-        LOG(log_debug, logtype_afpd, "initvol_vfs: Enabling EA support with Solaris native EAs.");
-
-        netatalk_adouble.list_eas = sol_list_eas;
-        netatalk_adouble.get_easize = sol_get_easize;
-        netatalk_adouble.get_eacontent = sol_get_eacontent;
-        netatalk_adouble.set_ea = sol_set_ea;
-        netatalk_adouble.remove_ea = sol_remove_ea;
-#else
-        LOG(log_error, logtype_afpd, "initvol_vfs: Can't enable Solaris EA support.");
-        goto enable_adea;
-#endif
+    if (vol->v_vfs_ea == AFPVOL_EA_SYS) {
+        LOG(log_debug, logtype_afpd, "initvol_vfs: enabling EA support with native EAs");
+        vol->vfs_modules[1] = &netatalk_ea_sys;
+    } else if (vol->v_vfs_ea == AFPVOL_EA_AD) {
+        LOG(log_debug, logtype_afpd, "initvol_vfs: enabling EA support with adouble files");
+        vol->vfs_modules[1] = &netatalk_ea_adouble;
     } else {
-    enable_adea:
-        /* default: AFPVOL_EA_AD */
-        LOG(log_debug, logtype_afpd, "initvol_vfs: Enabling EA support with adouble files.");
-
-        netatalk_adouble.set_ea = set_ea;
-        netatalk_adouble.list_eas = list_eas;
-        netatalk_adouble.get_easize = get_easize;
-        netatalk_adouble.get_eacontent = get_eacontent;
-        netatalk_adouble.remove_ea = remove_ea;
+        LOG(log_debug, logtype_afpd, "initvol_vfs: volume without EA support");
     }
-}
 
+    /* ACLs */
+#ifdef HAVE_NFSv4_ACLS
+    vol->vfs_modules[2] = &netatalk_solaris_acl_adouble;
+#endif
+}