From 6dcaba1d633e3cfa96243c62ad8399f7e5558c48 Mon Sep 17 00:00:00 2001 From: franklahm Date: Fri, 2 Oct 2009 09:32:40 +0000 Subject: [PATCH] Extended Attributes support via files in .AppleDouble. Extended VFS stack, so apps linking libatalk can also actually use adouble and EA code. Missing: EA integration into AFP rm, cp, mv. --- configure.in | 30 +- etc/afpd/Makefile.am | 7 +- etc/afpd/acls.c | 89 +- etc/afpd/auth.c | 14 +- etc/afpd/directory.c | 52 +- etc/afpd/directory.h | 55 +- etc/afpd/enumerate.c | 6 +- etc/afpd/extattrs.c | 307 +----- etc/afpd/extattrs.h | 17 +- etc/afpd/file.c | 9 +- etc/afpd/filedir.c | 7 +- etc/afpd/hash.h | 170 +-- etc/afpd/unix.c | 108 +- etc/afpd/unix.h | 8 +- etc/afpd/volume.c | 30 +- etc/afpd/volume.h | 197 +--- include/atalk/Makefile.am | 4 +- include/atalk/adouble.h | 12 +- include/atalk/afp.h | 2 +- include/atalk/directory.h | 87 ++ include/atalk/ea.h | 110 ++ include/atalk/hash.h | 179 +++ include/atalk/util.h | 52 +- include/atalk/vfs.h | 90 ++ include/atalk/volinfo.h | 3 +- include/atalk/volume.h | 209 ++++ libatalk/Makefile.am | 12 +- libatalk/adouble/Makefile.am | 3 +- libatalk/adouble/ad_open.c | 4 +- libatalk/util/Makefile.am | 3 +- libatalk/util/locking.c | 49 + libatalk/util/volinfo.c | 1 - libatalk/vfs/.cvsignore | 7 + libatalk/vfs/Makefile.am | 10 + libatalk/vfs/acl.c | 110 ++ libatalk/vfs/ea.c | 1509 ++++++++++++++++++++++++++ libatalk/vfs/unix.c | 155 +++ libatalk/vfs/vfs.c | 862 +++++++++++++++ macros/summary.m4 | 4 +- man/man5/AppleVolumes.default.5.tmpl | 811 ++++++++------ 40 files changed, 4061 insertions(+), 1333 deletions(-) create mode 100644 include/atalk/directory.h create mode 100644 include/atalk/ea.h create mode 100644 include/atalk/hash.h create mode 100644 include/atalk/vfs.h create mode 100644 include/atalk/volume.h create mode 100644 libatalk/util/locking.c create mode 100644 libatalk/vfs/.cvsignore create mode 100644 libatalk/vfs/Makefile.am create mode 100644 libatalk/vfs/acl.c create mode 100644 libatalk/vfs/ea.c create mode 100644 libatalk/vfs/unix.c create mode 100644 libatalk/vfs/vfs.c diff --git a/configure.in b/configure.in index 5380f6d2..88857034 100644 --- a/configure.in +++ b/configure.in @@ -1,4 +1,4 @@ -dnl $Id: configure.in,v 1.219 2009-09-18 12:36:39 franklahm Exp $ +dnl $Id: configure.in,v 1.220 2009-10-02 09:32:40 franklahm Exp $ dnl configure.in for netatalk AC_INIT(etc/afpd/main.c) @@ -1043,25 +1043,14 @@ fi AC_SUBST(LIBATALK_ACLS) dnl --------------------- check for Extended Attributes support -neta_cv_extattrs="no" -AC_MSG_CHECKING([if Extended Attribute Support should be enabled]) -AC_ARG_ENABLE(extattrs, - [ --enable-extattrs enable Extended Attributes],[ - if test x"$enableval" = x"yes"; then - AC_MSG_RESULT([yes]) - neta_cv_extattrs="yes" - else - AC_MSG_RESULT([no]) - fi],[ - AC_MSG_RESULT([no]) - ] -) -if test x$neta_cv_extattrs = xyes; then - AC_CHECK_LIB(c,attropen,neta_cv_extattrs=yes,neta_cv_extattrs=no) -fi -if test x$neta_cv_extattrs = xyes; then - AC_MSG_NOTICE([Enabling Extended Attributes support]) - AC_DEFINE([HAVE_EXT_ATTRS], 1, [Enable Extended Attributes]) +neta_cv_eas="adouble" +if test "x$this_os" = "xsolaris"; then + AC_CHECK_LIB(c,attropen, [ + neta_cv_eas="$neta_cv_eas Solaris" + AC_MSG_NOTICE([Enabling Solaris Extended Attributes support]) + AC_DEFINE([HAVE_SOLARIS_EAS], 1, [Enable Extended Attributes]) + ] + ) fi dnl --------------------- last minute substitutions @@ -1170,6 +1159,7 @@ AC_OUTPUT([Makefile libatalk/tdb/Makefile libatalk/unicode/Makefile libatalk/unicode/charsets/Makefile + libatalk/vfs/Makefile macros/Makefile man/Makefile man/man1/Makefile diff --git a/etc/afpd/Makefile.am b/etc/afpd/Makefile.am index 99cf63f0..87228109 100644 --- a/etc/afpd/Makefile.am +++ b/etc/afpd/Makefile.am @@ -8,22 +8,19 @@ afpd_SOURCES = unix.c ofork.c main.c switch.c auth.c volume.c directory.c \ file.c enumerate.c desktop.c filedir.c fork.c appl.c gettok.c \ mangle.c status.c afp_options.c afp_asp.c afp_dsi.c messages.c \ afp_config.c nfsquota.c quota.c uam.c afs.c uid.c afp_util.c \ - catsearch.c afprun.c vfs_adouble.c hash.c + catsearch.c afprun.c hash.c extattrs.c if USE_NFSv4_ACLS afpd_SOURCES += acls.c endif -if USE_EXT_ATTRS -afpd_SOURCES += extattrs.c -endif afpd_LDADD = $(top_builddir)/libatalk/cnid/libcnid.la $(top_builddir)/libatalk/libatalk.la afpd_LDFLAGS = -export-dynamic noinst_HEADERS = auth.h afp_config.h desktop.h directory.h file.h \ filedir.h fork.h globals.h icon.h mangle.h misc.h status.h switch.h \ - uam_auth.h uid.h unix.h volume.h afp_vfs.h hash.h acls.h acl_mappings.h + uam_auth.h uid.h unix.h volume.h hash.h acls.h acl_mappings.h LIBS = @LIBS@ @QUOTA_LIBS@ @SLP_LIBS@ @WRAP_LIBS@ @LIBADD_DL@ diff --git a/etc/afpd/acls.c b/etc/afpd/acls.c index fd408aa0..2da2816c 100644 --- a/etc/afpd/acls.c +++ b/etc/afpd/acls.c @@ -1,5 +1,5 @@ /* - $Id: acls.c,v 1.3 2009-02-26 14:00:18 franklahm Exp $ + $Id: acls.c,v 1.4 2009-10-02 09:32:40 franklahm Exp $ Copyright (c) 2008,2009 Frank Lahm This program is free software; you can redistribute it and/or modify @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -49,38 +50,6 @@ * Basic and helper funcs ********************************************************/ -/* Get ACL. Allocates storage as needed. Caller must free. - * Returns no of ACEs or -1 on error. */ -static int get_nfsv4_acl(const char *name, ace_t **retAces) -{ - int ace_count = -1; - ace_t *aces; - - *retAces = NULL; - ace_count = acl(name, ACE_GETACLCNT, 0, NULL); - if (ace_count <= 0) { - LOG(log_error, logtype_afpd, "get_nfsv4_acl: acl(ACE_GETACLCNT) error"); - return -1; - } - - aces = malloc(ace_count * sizeof(ace_t)); - if (aces == NULL) { - LOG(log_error, logtype_afpd, "get_nfsv4_acl: malloc error"); - return -1; - } - - if ( (acl(name, ACE_GETACL, ace_count, aces)) == -1 ) { - LOG(log_error, logtype_afpd, "get_nfsv4_acl: acl(ACE_GETACL) error"); - free(aces); - return -1; - } - - LOG(log_debug9, logtype_afpd, "get_nfsv4_acl: file: %s -> No. of ACEs: %d", name, ace_count); - *retAces = aces; - - return ace_count; -} - /* Takes a users name, uid and primary gid and checks if user is member of any group Returns -1 if no or error, 0 if yes @@ -459,60 +428,6 @@ cleanup: return err; } -/* Removes all non-trivial ACLs from object. Returns full AFPERR code. */ -int remove_acl(const char *name) -{ - int ret,i, ace_count, trivial_aces, new_aces_count; - ace_t *old_aces = NULL; - ace_t *new_aces = NULL; - - LOG(log_debug9, logtype_afpd, "remove_acl: BEGIN"); - - /* Get existing ACL and count trivial ACEs */ - if ((ace_count = get_nfsv4_acl(name, &old_aces)) == -1) - return AFPERR_MISC; - trivial_aces = 0; - for ( i=0; i < ace_count; i++) { - if (old_aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE)) - trivial_aces++; - } - - /* malloc buffer for new ACL */ - if ((new_aces = malloc(trivial_aces * sizeof(ace_t))) == NULL) { - LOG(log_error, logtype_afpd, "remove_acl: malloc %s", strerror(errno)); - ret = AFPERR_MISC; - goto exit; - } - - /* Now copy the trivial ACEs */ - new_aces_count = 0; - for (i=0; i < ace_count; i++) { - if (old_aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE)) { - memcpy(&new_aces[new_aces_count], &old_aces[i], sizeof(ace_t)); - new_aces_count++; - } - } - - if ( (acl(name, ACE_SETACL, trivial_aces, new_aces)) == 0) - ret = AFP_OK; - else { - LOG(log_error, logtype_afpd, "set_acl: error setting acl: %s", strerror(errno)); - if (errno == (EACCES | EPERM)) - ret = AFPERR_ACCESS; - else if (errno == ENOENT) - ret = AFPERR_NOITEM; - else - ret = AFPERR_MISC; - } - -exit: - free(old_aces); - free(new_aces); - - LOG(log_debug9, logtype_afpd, "remove_acl: END"); - return ret; -} - /* Removes all non-trivial ACLs from object. Returns full AFPERR code. */ static int remove_acl_vfs(const struct vol *vol,const char *path, int dir) { diff --git a/etc/afpd/auth.c b/etc/afpd/auth.c index ea09460a..e86e0ffd 100644 --- a/etc/afpd/auth.c +++ b/etc/afpd/auth.c @@ -1,5 +1,5 @@ /* - * $Id: auth.c,v 1.65 2009-09-28 12:04:51 franklahm Exp $ + * $Id: auth.c,v 1.66 2009-10-02 09:32:40 franklahm Exp $ * * Copyright (c) 1990,1993 Regents of The University of Michigan. * All Rights Reserved. See COPYRIGHT. @@ -47,12 +47,10 @@ extern void afp_get_cmdline( int *ac, char ***av ); #include "switch.h" #include "status.h" #include "fork.h" +#include "extattrs.h" #ifdef HAVE_NFSv4_ACLS #include "acls.h" #endif -#ifdef HAVE_EXT_ATTRS -#include "extattrs.h" -#endif int afp_version = 11; static int afp_version_index; @@ -202,21 +200,19 @@ static int set_auth_switch(int expired) afp_switch = postauth_switch; switch (afp_version) { case 32: - uam_afpserver_action(AFP_SPOTLIGHT_PRIVATE, UAM_AFPSERVER_POSTAUTH, afp_null_nolog, NULL); - uam_afpserver_action(AFP_SYNCDIR, UAM_AFPSERVER_POSTAUTH, afp_syncdir, NULL); - uam_afpserver_action(AFP_SYNCFORK, UAM_AFPSERVER_POSTAUTH, afp_syncfork, NULL); #ifdef HAVE_NFSv4_ACLS uam_afpserver_action(AFP_GETACL, UAM_AFPSERVER_POSTAUTH, afp_getacl, NULL); uam_afpserver_action(AFP_SETACL, UAM_AFPSERVER_POSTAUTH, afp_setacl, NULL); uam_afpserver_action(AFP_ACCESS, UAM_AFPSERVER_POSTAUTH, afp_access, NULL); #endif -#ifdef HAVE_EXT_ATTRS uam_afpserver_action(AFP_GETEXTATTR, UAM_AFPSERVER_POSTAUTH, afp_getextattr, NULL); uam_afpserver_action(AFP_SETEXTATTR, UAM_AFPSERVER_POSTAUTH, afp_setextattr, NULL); uam_afpserver_action(AFP_REMOVEATTR, UAM_AFPSERVER_POSTAUTH, afp_remextattr, NULL); uam_afpserver_action(AFP_LISTEXTATTR, UAM_AFPSERVER_POSTAUTH, afp_listextattr, NULL); -#endif case 31: + uam_afpserver_action(AFP_SYNCDIR, UAM_AFPSERVER_POSTAUTH, afp_syncdir, NULL); + uam_afpserver_action(AFP_SYNCFORK, UAM_AFPSERVER_POSTAUTH, afp_syncfork, NULL); + uam_afpserver_action(AFP_SPOTLIGHT_PRIVATE, UAM_AFPSERVER_POSTAUTH, afp_null_nolog, NULL); uam_afpserver_action(AFP_ENUMERATE_EXT2, UAM_AFPSERVER_POSTAUTH, afp_enumerate_ext2, NULL); case 30: uam_afpserver_action(AFP_ENUMERATE_EXT, UAM_AFPSERVER_POSTAUTH, afp_enumerate_ext, NULL); diff --git a/etc/afpd/directory.c b/etc/afpd/directory.c index c5049a36..e847a9d7 100644 --- a/etc/afpd/directory.c +++ b/etc/afpd/directory.c @@ -1,5 +1,5 @@ /* - * $Id: directory.c,v 1.104 2009-09-14 00:02:21 didg Exp $ + * $Id: directory.c,v 1.105 2009-10-02 09:32:40 franklahm Exp $ * * Copyright (c) 1990,1993 Regents of The University of Michigan. * All Rights Reserved. See COPYRIGHT. @@ -38,8 +38,9 @@ char *strchr (), *strrchr (); #include #include #include -#include +#include +#include #include #include #include @@ -55,6 +56,7 @@ char *strchr (), *strrchr (); #include "globals.h" #include "unix.h" #include "mangle.h" +#include "hash.h" #ifdef HAVE_NFSv4_ACLS extern void addir_inherit_acl(const struct vol *vol); @@ -725,30 +727,6 @@ struct path *path; return( dir ); } -/* ------------------- - system rmdir with afp error code. - ENOENT is not an error. - */ -int netatalk_rmdir(const char *name) -{ - if (rmdir(name) < 0) { - switch ( errno ) { - case ENOENT : - break; - case ENOTEMPTY : - return AFPERR_DIRNEMPT; - case EPERM: - case EACCES : - return AFPERR_ACCESS; - case EROFS: - return AFPERR_VLOCK; - default : - return AFPERR_PARAM; - } - } - return AFP_OK; -} - /* ------------------------- appledouble mkdir afp error code. */ @@ -775,28 +753,6 @@ static int netatalk_mkdir(const char *name) return AFP_OK; } -/* ------------------- - system unlink with afp error code. - ENOENT is not an error. - */ -int netatalk_unlink(const char *name) -{ - if (unlink(name) < 0) { - switch (errno) { - case ENOENT : - break; - case EROFS: - return AFPERR_VLOCK; - case EPERM: - case EACCES : - return AFPERR_ACCESS; - default : - return AFPERR_PARAM; - } - } - return AFP_OK; -} - /* ------------------- */ static int deletedir(char *dir) { diff --git a/etc/afpd/directory.h b/etc/afpd/directory.h index 577401d4..5d0b75f4 100644 --- a/etc/afpd/directory.h +++ b/etc/afpd/directory.h @@ -1,5 +1,5 @@ /* - * $Id: directory.h,v 1.27 2009-09-14 00:02:21 didg Exp $ + * $Id: directory.h,v 1.28 2009-10-02 09:32:40 franklahm Exp $ * * Copyright (c) 1990,1991 Regents of The University of Michigan. * All Rights Reserved. @@ -38,61 +38,14 @@ #include #endif +#include + #include "globals.h" #include "volume.h" -/* the did tree is now a red-black tree while the parent/child - * tree is a circular doubly-linked list. how exciting. */ -struct dir { - struct dir *d_left, *d_right, *d_back; /* for red-black tree */ - int d_color; - struct dir *d_parent, *d_child; /* parent-child */ - struct dir *d_prev, *d_next; /* siblings */ - void *d_ofork; /* oforks using this directory. */ - u_int32_t d_did; - int d_flags; - - time_t ctime; /* inode ctime */ - u_int32_t offcnt; /* offspring count */ - - char *d_m_name; /* mac name */ - char *d_u_name; /* unix name */ - ucs2_t *d_m_name_ucs2; /* mac name as UCS2 */ -}; - -struct path { - int m_type; /* mac name type (long name, unicode */ - char *m_name; /* mac name */ - char *u_name; /* unix name */ - cnid_t id; /* file id (only for getmetadata) */ - struct dir *d_dir; /* */ - int st_valid; /* does st_errno and st set */ - int st_errno; - struct stat st; -}; - -static inline int path_isadir(struct path *o_path) -{ - return o_path->d_dir != NULL; -#if 0 - return o_path->m_name == '\0' || /* we are in a it */ - !o_path->st_valid || /* in cache but we can't chdir in it */ - (!o_path->st_errno && S_ISDIR(o_path->st.st_mode)); /* not in cache an can't chdir */ -#endif -} - #define DIRTREE_COLOR_RED 0 #define DIRTREE_COLOR_BLACK 1 -/* setgid directories */ -#ifndef DIRBITS -# ifdef AFS -# define DIRBITS 0 -# else /* AFS */ -# define DIRBITS S_ISGID -# endif /* AFS */ -#endif /* DIRBITS */ - #define DIRF_FSMASK (3<<0) #define DIRF_NOFS (0<<0) #define DIRF_AFS (1<<0) @@ -101,7 +54,6 @@ static inline int path_isadir(struct path *o_path) #define DIRF_OFFCNT (1<<4) /* offsprings count is valid */ #define DIRF_CNID (1<<5) /* renumerate id */ - #define AFPDIR_READ (1<<0) /* directory bits */ @@ -194,7 +146,6 @@ extern int for_each_dirent __P((const struct vol *, char *, dir_loop , void *)) extern int check_access __P((char *name , int mode)); extern int file_access __P((struct path *path, int mode)); -extern int netatalk_rmdir __P((const char *name)); extern int netatalk_unlink __P((const char *name)); extern int caseenumerate __P((const struct vol *, struct path *, struct dir *)); diff --git a/etc/afpd/enumerate.c b/etc/afpd/enumerate.c index 151394f5..c994f03c 100644 --- a/etc/afpd/enumerate.c +++ b/etc/afpd/enumerate.c @@ -1,5 +1,5 @@ /* - * $Id: enumerate.c,v 1.43 2005-04-30 21:33:41 didg Exp $ + * $Id: enumerate.c,v 1.44 2009-10-02 09:32:40 franklahm Exp $ * * Copyright (c) 1990,1993 Regents of The University of Michigan. * All Rights Reserved. See COPYRIGHT. @@ -13,13 +13,13 @@ #include #include #include - -#include #include #include +#include #include #include +#include #include #include "desktop.h" #include "directory.h" diff --git a/etc/afpd/extattrs.c b/etc/afpd/extattrs.c index 9b20e233..7748480a 100644 --- a/etc/afpd/extattrs.c +++ b/etc/afpd/extattrs.c @@ -1,5 +1,5 @@ /* - $Id: extattrs.c,v 1.3 2009-08-31 14:50:46 franklahm Exp $ + $Id: extattrs.c,v 1.4 2009-10-02 09:32:40 franklahm Exp $ Copyright (c) 2009 Frank Lahm This program is free software; you can redistribute it and/or modify @@ -13,17 +13,12 @@ GNU General Public License for more details. */ -/* According to man fsattr.5 we must define _ATFILE_SOURCE */ -#define _ATFILE_SOURCE - #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ -#ifdef HAVE_EXT_ATTRS - -#include #include +#include #include #include #include @@ -32,8 +27,10 @@ #include #include +#include #include #include +#include #include "globals.h" #include "volume.h" @@ -46,7 +43,6 @@ static char *ea_finderinfo = "com.apple.FinderInfo"; static char *ea_resourcefork = "com.apple.ResourceFork"; /* This should be big enough to consecutively store the names of all attributes */ -#define ATTRNAMEBUFSIZ 4096 static char attrnamebuf[ATTRNAMEBUFSIZ]; static void hexdump(void *m, size_t l) { @@ -67,186 +63,8 @@ static void hexdump(void *m, size_t l) { } } -static int getextattr_size(char *rbuf, int *rbuflen, char *uname, int oflag, char *attruname) -{ - int ret, attrdirfd; - uint32_t attrsize; - struct stat st; - - LOG(log_debug7, logtype_afpd, "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, "getextattr_size(%s): encountered symlink with kXAttrNoFollow", uname); - - memset(rbuf, 0, 4); - *rbuflen += 4; - - return AFP_OK; - } - LOG(log_error, logtype_afpd, "getextattr_size: attropen error: %s", strerror(errno)); - return AFPERR_MISC; - } - - if ( -1 == (fstatat(attrdirfd, attruname, &st, 0))) { - LOG(log_error, logtype_afpd, "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, "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; -} - -static int getextattr_content(char *rbuf, int *rbuflen, char *uname, int oflag, char *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, "getextattr_content(%s): encountered symlink with kXAttrNoFollow", uname); - - memset(rbuf, 0, 4); - *rbuflen += 4; - - return AFP_OK; - } - LOG(log_error, logtype_afpd, "getextattr_content(%s): attropen error: %s", attruname, strerror(errno)); - return AFPERR_MISC; - } - - if ( -1 == (fstat(attrdirfd, &st))) { - LOG(log_error, logtype_afpd, "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, "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, "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; -} - -int list_extattr(AFPObj *obj, char *attrnamebuf, int *buflen, char *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, "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, "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); - - if ( 0 >= ( len = convert_string(obj->options.unixcharset, 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, "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, "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; -} - /*************************************** - * Interface + * AFP funcs ****************************************/ /* @@ -258,7 +76,7 @@ int afp_listextattr(AFPObj *obj, char *ibuf, int ibuflen _U_, char *rbuf, int *r { int count, ret, oflag = 0; uint16_t vid, bitmap; - uint32_t did, maxreply; + uint32_t did, maxreply, tmpattr; struct vol *vol; struct dir *dir; struct path *s_path; @@ -267,10 +85,6 @@ int afp_listextattr(AFPObj *obj, char *ibuf, int ibuflen _U_, char *rbuf, int *r char *uname, *FinderInfo; static int buf_valid = 0, attrbuflen = 0; -#ifdef DEBUG - LOG(log_debug9, logtype_afpd, "afp_listextattr: BEGIN"); -#endif - *rbuflen = 0; ibuf += 2; @@ -304,9 +118,10 @@ int afp_listextattr(AFPObj *obj, char *ibuf, int ibuflen _U_, char *rbuf, int *r bitmap = ntohs( bitmap ); ibuf += 2; +#ifdef HAVE_SOLARIS_EAS if (bitmap & kXAttrNoFollow) oflag = O_NOFOLLOW; - +#endif /* Skip ReqCount, StartIndex and maxreply*/ ibuf += 10; @@ -368,7 +183,8 @@ int afp_listextattr(AFPObj *obj, char *ibuf, int ibuflen _U_, char *rbuf, int *r attrbuflen += strlen(ea_resourcefork) + 1; } - ret = list_extattr(obj, attrnamebuf, &attrbuflen, uname, oflag); + ret = vol->vfs->list_eas(vol, attrnamebuf, &attrbuflen, uname, oflag); + switch (ret) { case AFPERR_BADTYPE: /* its a symlink and client requested O_NOFOLLOW */ @@ -391,8 +207,8 @@ int afp_listextattr(AFPObj *obj, char *ibuf, int ibuflen _U_, char *rbuf, int *r rbuf += 2; *rbuflen += 2; - attrbuflen = htonl(attrbuflen); - memcpy( rbuf, &attrbuflen, 4); + tmpattr = htonl(attrbuflen); + memcpy( rbuf, &tmpattr, 4); rbuf += 4; *rbuflen += 4; @@ -412,7 +228,6 @@ exit: if (adp) ad_close_metadata( adp); - LOG(log_debug9, logtype_afpd, "afp_listextattr: END"); return ret; } @@ -427,10 +242,6 @@ int afp_getextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int struct path *s_path; -#ifdef DEBUG - LOG(log_debug9, logtype_afpd, "afp_getextattr: BEGIN"); -#endif - *rbuflen = 0; ibuf += 2; @@ -451,8 +262,11 @@ int afp_getextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int memcpy( &bitmap, ibuf, 2); bitmap = ntohs( bitmap ); ibuf += 2; + +#ifdef HAVE_SOLARIS_EAS if (bitmap & kXAttrNoFollow) - oflag = AT_SYMLINK_NOFOLLOW; + oflag = O_NOFOLLOW; +#endif /* Skip Offset and ReqCount */ ibuf += 16; @@ -505,20 +319,16 @@ int afp_getextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int if its non 0 we must return the attribute. */ if (maxreply == 0) - ret = getextattr_size(rbuf, rbuflen, s_path->u_name, oflag, attruname); + ret = vol->vfs->get_easize(vol, rbuf, rbuflen, s_path->u_name, oflag, attruname); else - ret = getextattr_content(rbuf, rbuflen, s_path->u_name, oflag, attruname, maxreply); - -#ifdef DEBUG - LOG(log_debug9, logtype_afpd, "afp_getextattr: END"); -#endif + ret = vol->vfs->get_eacontent(vol, rbuf, rbuflen, s_path->u_name, oflag, attruname, maxreply); return ret; } int afp_setextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen) { - int len, oflag = O_CREAT | O_WRONLY, attrdirfd; + int oflag = O_CREAT | O_WRONLY, ret; uint16_t vid, bitmap; uint32_t did, attrnamelen, attrsize; char attrmname[256], attruname[256]; @@ -526,10 +336,6 @@ int afp_setextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int struct dir *dir; struct path *s_path; -#ifdef DEBUG - LOG(log_debug9, logtype_afpd, "afp_setextattr: BEGIN"); -#endif - *rbuflen = 0; ibuf += 2; @@ -550,8 +356,12 @@ int afp_setextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int memcpy( &bitmap, ibuf, 2); bitmap = ntohs( bitmap ); ibuf += 2; + +#ifdef HAVE_SOLARIS_EAS if (bitmap & kXAttrNoFollow) oflag |= AT_SYMLINK_NOFOLLOW; +#endif + if (bitmap & kXAttrCreate) oflag |= O_EXCL; else if (bitmap & kXAttrReplace) @@ -599,38 +409,14 @@ int afp_setextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int LOG(log_debug, logtype_afpd, "afp_setextattr(%s): EA: %s, size: %u", s_path->u_name, attrmname, attrsize); - if ( -1 == (attrdirfd = attropen(s_path->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; - } - - while (1) { - len = write(attrdirfd, ibuf, attrsize); - if (len == -1) { - LOG(log_error, logtype_afpd, "afp_setextattr(%s): read error: %s", attruname, strerror(errno)); - return AFPERR_MISC; - } - attrsize -= len; - ibuf += len; - if (attrsize == 0) - break; - } - -#ifdef DEBUG - LOG(log_debug9, logtype_afpd, "afp_setextattr: END"); -#endif + ret = vol->vfs->set_ea(vol, s_path->u_name, attruname, ibuf, attrsize, oflag); - return AFP_OK; + return ret; } int afp_remextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen) { - int oflag = O_RDONLY, attrdirfd; + int oflag = O_RDONLY, ret; uint16_t vid, bitmap; uint32_t did, attrnamelen; char attrmname[256], attruname[256]; @@ -638,10 +424,6 @@ int afp_remextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int struct dir *dir; struct path *s_path; -#ifdef DEBUG - LOG(log_debug9, logtype_afpd, "afp_remextattr: BEGIN"); -#endif - *rbuflen = 0; ibuf += 2; @@ -662,8 +444,11 @@ int afp_remextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int memcpy( &bitmap, ibuf, 2); bitmap = ntohs( bitmap ); ibuf += 2; + +#ifdef HAVE_SOLARIS_EAS if (bitmap & kXAttrNoFollow) oflag |= AT_SYMLINK_NOFOLLOW; +#endif /* get name */ if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) { @@ -696,38 +481,8 @@ int afp_remextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int LOG(log_debug, logtype_afpd, "afp_remextattr(%s): EA: %s", s_path->u_name, attrmname); - if ( -1 == (attrdirfd = attropen(s_path->u_name, ".", 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; - } - -#ifdef DEBUG - LOG(log_debug9, logtype_afpd, "afp_remextattr: END"); -#endif + ret = vol->vfs->remove_ea(vol, s_path->u_name, attruname, oflag); - return AFP_OK; + return ret; } - - -#endif /* HAVE_EXT_ATTRS */ - diff --git a/etc/afpd/extattrs.h b/etc/afpd/extattrs.h index 82d9befa..90b034bd 100644 --- a/etc/afpd/extattrs.h +++ b/etc/afpd/extattrs.h @@ -1,5 +1,5 @@ /* - $Id: extattrs.h,v 1.1 2009-02-16 13:49:20 franklahm Exp $ + $Id: extattrs.h,v 1.2 2009-10-02 09:32:40 franklahm Exp $ Copyright (c) 2009 Frank Lahm This program is free software; you can redistribute it and/or modify @@ -16,20 +16,7 @@ #ifndef AFPD_EXT_ATTRS_H #define AFPD_EXT_ATTRS_H -/* This seems to be the current limit fo HFS+, we arbitrarily force that - which also safes us from buffer overflows */ -#define MAX_EA_SIZE 3802 - -/* At time of writing the 10.5.6 client adds 8 bytes to the - length of the EA that we send him */ -#define MAX_REPLY_EXTRA_BYTES 8 - -enum { - kXAttrNoFollow = 0x1, - kXAttrCreate = 0x2, - kXAttrReplace = 0x4 -}; - +/* AFP funcs */ extern int afp_listextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen); extern int afp_getextattr(AFPObj *obj _U_ , char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen); extern int afp_setextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen); diff --git a/etc/afpd/file.c b/etc/afpd/file.c index 38672a73..f2b97209 100644 --- a/etc/afpd/file.c +++ b/etc/afpd/file.c @@ -1,5 +1,5 @@ /* - * $Id: file.c,v 1.110 2009-09-21 12:35:05 franklahm Exp $ + * $Id: file.c,v 1.111 2009-10-02 09:32:40 franklahm Exp $ * * Copyright (c) 1990,1993 Regents of The University of Michigan. * All Rights Reserved. See COPYRIGHT. @@ -28,15 +28,14 @@ char *strchr (), *strrchr (); #endif /* ! HAVE_MEMCPY */ #endif /* STDC_HEADERS */ -#include #include #include #include - -#include #include - +#include +#include +#include #include #include #include diff --git a/etc/afpd/filedir.c b/etc/afpd/filedir.c index f9a84039..8c602b08 100644 --- a/etc/afpd/filedir.c +++ b/etc/afpd/filedir.c @@ -1,5 +1,5 @@ /* - * $Id: filedir.c,v 1.54 2009-09-01 13:15:13 franklahm Exp $ + * $Id: filedir.c,v 1.55 2009-10-02 09:32:40 franklahm Exp $ * * Copyright (c) 1990,1993 Regents of The University of Michigan. * All Rights Reserved. See COPYRIGHT. @@ -32,8 +32,9 @@ char *strchr (), *strrchr (); #include #include #include -#include +#include +#include #include #include #include @@ -755,7 +756,7 @@ int ibuflen _U_, *rbuflen; if (!isdir && !vol_unix_priv(vol)) { int admode = ad_mode("", 0777) | vol->v_fperm; - setfilmode(upath, admode, NULL); + setfilmode(upath, admode, NULL, vol->v_umask); vol->vfs->rf_setfilmode(vol, upath, admode, NULL); } setvoltime(obj, vol ); diff --git a/etc/afpd/hash.h b/etc/afpd/hash.h index 02adafba..dcac14b6 100644 --- a/etc/afpd/hash.h +++ b/etc/afpd/hash.h @@ -14,7 +14,7 @@ * into proprietary software; there is no requirement for such software to * contain a copyright notice related to this source. * - * $Id: hash.h,v 1.1 2005-04-30 21:33:41 didg Exp $ + * $Id: hash.h,v 1.2 2009-10-02 09:32:40 franklahm Exp $ * $Name: $ */ @@ -22,169 +22,7 @@ #define HASH_H #include -#ifdef KAZLIB_SIDEEFFECT_DEBUG -#include "sfx.h" -#endif - -/* - * Blurb for inclusion into C++ translation units - */ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef unsigned long hashcount_t; -#define HASHCOUNT_T_MAX ULONG_MAX - -typedef unsigned long hash_val_t; -#define HASH_VAL_T_MAX ULONG_MAX - -extern int hash_val_t_bit; - -#ifndef HASH_VAL_T_BIT -#define HASH_VAL_T_BIT ((int) hash_val_t_bit) -#endif - -/* - * Hash chain node structure. - * Notes: - * 1. This preprocessing directive is for debugging purposes. The effect is - * that if the preprocessor symbol KAZLIB_OPAQUE_DEBUG is defined prior to the - * inclusion of this header, then the structure shall be declared as having - * the single member int __OPAQUE__. This way, any attempts by the - * client code to violate the principles of information hiding (by accessing - * the structure directly) can be diagnosed at translation time. However, - * note the resulting compiled unit is not suitable for linking. - * 2. This is a pointer to the next node in the chain. In the last node of a - * chain, this pointer is null. - * 3. The key is a pointer to some user supplied data that contains a unique - * identifier for each hash node in a given table. The interpretation of - * the data is up to the user. When creating or initializing a hash table, - * the user must supply a pointer to a function for comparing two keys, - * and a pointer to a function for hashing a key into a numeric value. - * 4. The value is a user-supplied pointer to void which may refer to - * any data object. It is not interpreted in any way by the hashing - * module. - * 5. The hashed key is stored in each node so that we don't have to rehash - * each key when the table must grow or shrink. - */ - -typedef struct hnode_t { - #if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) /* 1 */ - struct hnode_t *hash_next; /* 2 */ - const void *hash_key; /* 3 */ - void *hash_data; /* 4 */ - hash_val_t hash_hkey; /* 5 */ - #else - int hash_dummy; - #endif -} hnode_t; - -/* - * The comparison function pointer type. A comparison function takes two keys - * and produces a value of -1 if the left key is less than the right key, a - * value of 0 if the keys are equal, and a value of 1 if the left key is - * greater than the right key. - */ - -typedef int (*hash_comp_t)(const void *, const void *); - -/* - * The hashing function performs some computation on a key and produces an - * integral value of type hash_val_t based on that key. For best results, the - * function should have a good randomness properties in *all* significant bits - * over the set of keys that are being inserted into a given hash table. In - * particular, the most significant bits of hash_val_t are most significant to - * the hash module. Only as the hash table expands are less significant bits - * examined. Thus a function that has good distribution in its upper bits but - * not lower is preferrable to one that has poor distribution in the upper bits - * but not the lower ones. - */ - -typedef hash_val_t (*hash_fun_t)(const void *); - -/* - * allocator functions - */ - -typedef hnode_t *(*hnode_alloc_t)(void *); -typedef void (*hnode_free_t)(hnode_t *, void *); - -/* - * This is the hash table control structure. It keeps track of information - * about a hash table, as well as the hash table itself. - * Notes: - * 1. Pointer to the hash table proper. The table is an array of pointers to - * hash nodes (of type hnode_t). If the table is empty, every element of - * this table is a null pointer. A non-null entry points to the first - * element of a chain of nodes. - * 2. This member keeps track of the size of the hash table---that is, the - * number of chain pointers. - * 3. The count member maintains the number of elements that are presently - * in the hash table. - * 4. The maximum count is the greatest number of nodes that can populate this - * table. If the table contains this many nodes, no more can be inserted, - * and the hash_isfull() function returns true. - * 5. The high mark is a population threshold, measured as a number of nodes, - * which, if exceeded, will trigger a table expansion. Only dynamic hash - * tables are subject to this expansion. - * 6. The low mark is a minimum population threshold, measured as a number of - * nodes. If the table population drops below this value, a table shrinkage - * will occur. Only dynamic tables are subject to this reduction. No table - * will shrink beneath a certain absolute minimum number of nodes. - * 7. This is the a pointer to the hash table's comparison function. The - * function is set once at initialization or creation time. - * 8. Pointer to the table's hashing function, set once at creation or - * initialization time. - * 9. The current hash table mask. If the size of the hash table is 2^N, - * this value has its low N bits set to 1, and the others clear. It is used - * to select bits from the result of the hashing function to compute an - * index into the table. - * 10. A flag which indicates whether the table is to be dynamically resized. It - * is set to 1 in dynamically allocated tables, 0 in tables that are - * statically allocated. - */ - -typedef struct hash_t { - #if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) - struct hnode_t **hash_table; /* 1 */ - hashcount_t hash_nchains; /* 2 */ - hashcount_t hash_nodecount; /* 3 */ - hashcount_t hash_maxcount; /* 4 */ - hashcount_t hash_highmark; /* 5 */ - hashcount_t hash_lowmark; /* 6 */ - hash_comp_t hash_compare; /* 7 */ - hash_fun_t hash_function; /* 8 */ - hnode_alloc_t hash_allocnode; - hnode_free_t hash_freenode; - void *hash_context; - hash_val_t hash_mask; /* 9 */ - int hash_dynamic; /* 10 */ - #else - int hash_dummy; - #endif -} hash_t; - -/* - * Hash scanner structure, used for traversals of the data structure. - * Notes: - * 1. Pointer to the hash table that is being traversed. - * 2. Reference to the current chain in the table being traversed (the chain - * that contains the next node that shall be retrieved). - * 3. Pointer to the node that will be retrieved by the subsequent call to - * hash_scan_next(). - */ - -typedef struct hscan_t { - #if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) - hash_t *hash_table; /* 1 */ - hash_val_t hash_chain; /* 2 */ - hnode_t *hash_next; /* 3 */ - #else - int hash_dummy; - #endif -} hscan_t; +#include extern hash_t *hash_create(hashcount_t, hash_comp_t, hash_fun_t); extern void hash_set_allocator(hash_t *, hnode_alloc_t, hnode_free_t, void *); @@ -233,8 +71,4 @@ extern void hnode_destroy(hnode_t *); #define hnode_put(N, V) ((N)->hash_data = (V)) #endif -#ifdef __cplusplus -} -#endif - #endif diff --git a/etc/afpd/unix.c b/etc/afpd/unix.c index 1ad282f2..ffe594a0 100644 --- a/etc/afpd/unix.c +++ b/etc/afpd/unix.c @@ -1,5 +1,5 @@ /* - * $Id: unix.c,v 1.52 2009-02-02 11:55:01 franklahm Exp $ + * $Id: unix.c,v 1.53 2009-10-02 09:32:40 franklahm Exp $ * * Copyright (c) 1990,1993 Regents of The University of Michigan. * All Rights Reserved. See COPYRIGHT. @@ -36,6 +36,7 @@ char *strchr (), *strrchr (); #include #include #include +#include #include #include @@ -249,78 +250,6 @@ struct maccess *ma; return( mode ); } -/* ----------------------------- */ -char *fullpathname(const char *name) -{ - static char wd[ MAXPATHLEN + 1]; - - if ( getcwd( wd , MAXPATHLEN) ) { - strlcat(wd, "/", MAXPATHLEN); - strlcat(wd, name, MAXPATHLEN); - } - else { - strlcpy(wd, name, MAXPATHLEN); - } - return wd; -} - -/* ----------------------------- - a dropbox is a folder where w is set but not r eg: - rwx-wx-wx or rwx-wx-- - rwx----wx (is not asked by a Mac with OS >= 8.0 ?) -*/ -int stickydirmode(name, mode, dropbox) -const char * name; -const mode_t mode; -const int dropbox; -{ - int retval = 0; - -#ifdef DROPKLUDGE - /* Turn on the sticky bit if this is a drop box, also turn off the setgid bit */ - if ((dropbox & AFPVOL_DROPBOX)) { - int uid; - - if ( ( (mode & S_IWOTH) && !(mode & S_IROTH)) || - ( (mode & S_IWGRP) && !(mode & S_IRGRP)) ) - { - uid=geteuid(); - if ( seteuid(0) < 0) { - LOG(log_error, logtype_afpd, "stickydirmode: unable to seteuid root: %s", strerror(errno)); - } - if ( (retval=chmod( name, ( (DIRBITS | mode | S_ISVTX) & ~default_options.umask) )) < 0) { - LOG(log_error, logtype_afpd, "stickydirmode: chmod \"%s\": %s", fullpathname(name), strerror(errno) ); - } else { -#ifdef DEBUG - LOG(log_info, logtype_afpd, "stickydirmode: (debug) chmod \"%s\": %s", fullpathname(name), strerror(retval) ); -#endif /* DEBUG */ - } - seteuid(uid); - return retval; - } - } -#endif /* DROPKLUDGE */ - - /* - * Ignore EPERM errors: We may be dealing with a directory that is - * group writable, in which case chmod will fail. - */ - if ( (chmod( name, (DIRBITS | mode) & ~default_options.umask ) < 0) && errno != EPERM && - !(errno == ENOENT && (dropbox & AFPVOL_NOADOUBLE)) ) - { - LOG(log_error, logtype_afpd, "stickydirmode: chmod \"%s\": %s", fullpathname(name), strerror(errno) ); - retval = -1; - } - - return retval; -} - -/* ------------------------- */ -int dir_rx_set(mode_t mode) -{ - return (mode & (S_IXUSR | S_IRUSR)) == (S_IXUSR | S_IRUSR); -} - #define EXEC_MODE (S_IXGRP | S_IXUSR | S_IXOTH) int setdeskmode( mode ) @@ -415,33 +344,12 @@ mode_t mode; mode |= vol->v_fperm; - if (setfilmode( path->u_name, mode, &path->st) < 0) + if (setfilmode( path->u_name, mode, &path->st, vol->v_umask) < 0) return -1; /* we need to set write perm if read set for resource fork */ return vol->vfs->rf_setfilmode(vol, path->u_name, mode, &path->st); } -/* --------------------- */ -int setfilmode(name, mode, st) -const char * name; -mode_t mode; -struct stat *st; -{ -struct stat sb; -mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO; /* rwx for owner group and other, by default */ - - if (!st) { - if (stat(name, &sb) != 0) - return -1; - st = &sb; - } - - mode |= st->st_mode & ~mask; /* keep other bits from previous mode */ - if ( chmod( name, mode & ~default_options.umask ) < 0 && errno != EPERM ) { - return -1; - } - return 0; -} /* --------------------- */ int setdirunixmode( vol, name, mode ) @@ -455,14 +363,14 @@ mode_t mode; if (dir_rx_set(mode)) { /* extending right? dir first then .AppleDouble in rf_setdirmode */ - if ( stickydirmode(name, DIRBITS | mode, dropbox) < 0 ) + if ( stickydirmode(name, DIRBITS | mode, dropbox, vol->v_umask) < 0 ) return -1; } if (vol->vfs->rf_setdirunixmode(vol, name, mode, NULL) < 0 && !vol_noadouble(vol)) { return -1 ; } if (!dir_rx_set(mode)) { - if ( stickydirmode(name, DIRBITS | mode, dropbox) < 0 ) + if ( stickydirmode(name, DIRBITS | mode, dropbox, vol->v_umask) < 0 ) return -1; } return 0; @@ -486,7 +394,7 @@ mode_t mode; if (dir_rx_set(mode)) { /* extending right? dir first */ - if ( stickydirmode(name, DIRBITS | mode, dropbox) < 0 ) + if ( stickydirmode(name, DIRBITS | mode, dropbox, vol->v_umask) < 0 ) return -1; } @@ -508,7 +416,7 @@ mode_t mode; if (!S_ISDIR(st.st_mode)) { int setmode = (osx && *dirp->d_name == '.')?hf_mode:mode; - if (setfilmode(dirp->d_name, setmode, &st) < 0) { + if (setfilmode(dirp->d_name, setmode, &st, vol->v_umask) < 0) { LOG(log_error, logtype_afpd, "setdirmode: chmod %s: %s",dirp->d_name, strerror(errno) ); return -1; } @@ -521,7 +429,7 @@ mode_t mode; } if (!dir_rx_set(mode)) { - if ( stickydirmode(name, DIRBITS | mode, dropbox) < 0 ) + if ( stickydirmode(name, DIRBITS | mode, dropbox, vol->v_umask) < 0 ) return -1; } return( 0 ); diff --git a/etc/afpd/unix.h b/etc/afpd/unix.h index d2e0a6fe..c319bcd6 100644 --- a/etc/afpd/unix.h +++ b/etc/afpd/unix.h @@ -1,5 +1,5 @@ /* - * $Id: unix.h,v 1.20 2005-06-02 12:32:18 didg Exp $ + * $Id: unix.h,v 1.21 2009-10-02 09:32:40 franklahm Exp $ */ #ifndef AFPD_UNIX_H @@ -221,15 +221,9 @@ extern int setdirunixmode __P((const struct vol *, const char *, mode_t)); extern int setdirmode __P((const struct vol *, const char *, mode_t)); extern int setdeskowner __P((const uid_t, const gid_t)); extern int setdirowner __P((const struct vol *, const char *, const uid_t, const gid_t)); -extern int setfilmode __P((const char *, mode_t , struct stat *)); extern int setfilunixmode __P((const struct vol *, struct path*, const mode_t)); extern int setfilowner __P((const struct vol *, const uid_t, const gid_t, struct path*)); -extern int unix_rename __P((const char *oldpath, const char *newpath)); -extern int dir_rx_set __P((mode_t mode)); -extern int stickydirmode __P((const char * name, const mode_t mode, const int dropbox)); - extern void accessmode __P((char *, struct maccess *, struct dir *, struct stat *)); -extern char *fullpathname __P((const char *)); #ifdef AFS #define accessmode afsmode diff --git a/etc/afpd/volume.c b/etc/afpd/volume.c index 979a2e29..a8ffaca9 100644 --- a/etc/afpd/volume.c +++ b/etc/afpd/volume.c @@ -1,5 +1,5 @@ /* - * $Id: volume.c,v 1.90 2009-09-11 09:14:16 franklahm Exp $ + * $Id: volume.c,v 1.91 2009-10-02 09:32:40 franklahm Exp $ * * Copyright (c) 1990,1993 Regents of The University of Michigan. * All Rights Reserved. See COPYRIGHT. @@ -125,6 +125,7 @@ m=u -> map both ways #define VOLOPT_DPERM 24 /* dperm default directories perms */ #define VOLOPT_FPERM 25 /* fperm default files perms */ #define VOLOPT_DFLTPERM 26 /* perm */ +#define VOLOPT_EA_VFS 27 /* Extended Attributes vfs indirection */ #define VOLOPT_MAX (VOLOPT_DFLTPERM +1) @@ -133,10 +134,11 @@ m=u -> map both ways #define VOLPASSLEN 8 #define VOLOPT_DEFAULT ":DEFAULT:" #define VOLOPT_DEFAULT_LEN 9 - struct vol_option { - char *c_value; - int i_value; - }; + +struct vol_option { + char *c_value; + int i_value; +}; typedef struct _special_folder { const char *name; @@ -181,7 +183,6 @@ static const _vol_opt_name vol_opt_names[] = { {AFPVOL_CASEINSEN, "CASEINSENSITIVE"}, /* volume is case insensitive */ {AFPVOL_EILSEQ, "ILLEGALSEQ"}, /* encode illegal sequence */ {AFPVOL_CACHE, "CACHEID"}, /* Use adouble v2 CNID caching. Default: yes */ - {AFPVOL_EXT_ATTRS, "EXT_ATTRS"}, /* Vol supports Extened Attributes */ {AFPVOL_ACLS, "ACLS"}, /* Vol supports ACLs */ {AFPVOL_TM, "TM"}, /* Set "kSupportsTMLockSteal" is volume attributes */ {0, NULL} @@ -491,8 +492,6 @@ static void volset(struct vol_option *options, struct vol_option *save, options[VOLOPT_ROOTPREEXEC].i_value = 1; else if (strcasecmp(p, "upriv") == 0) options[VOLOPT_FLAGS].i_value |= AFPVOL_UNIX_PRIV; - else if (strcasecmp(p, "extattrs") == 0) - options[VOLOPT_FLAGS].i_value |= AFPVOL_EXT_ATTRS; else if (strcasecmp(p, "acls") == 0) options[VOLOPT_FLAGS].i_value |= AFPVOL_ACLS; else if (strcasecmp(p, "nodev") == 0) @@ -553,6 +552,12 @@ static void volset(struct vol_option *options, struct vol_option *save, } else if (optionok(tmp, "denied_hosts:", val)) { setoption(options, save, VOLOPT_DENIED_HOSTS, val); + } else if (optionok(tmp, "ea:", val)) { + if (strcasecmp(val + 1, "ad") == 0) /* the default anyway */ + options[VOLOPT_EA_VFS].i_value = AFPVOL_EA_AD; + else if (strcasecmp(val + 1, "solaris") == 0) + options[VOLOPT_EA_VFS].i_value = AFPVOL_EA_SOLARIS; + } else { /* ignore unknown options */ LOG(log_debug, logtype_afpd, "ignoring unknown volume option: %s", tmp); @@ -706,6 +711,7 @@ static int creatvol(AFPObj *obj, struct passwd *pwd, /* os X start at 1 and use network order ie. 1 2 3 */ volume->v_vid = ++lastvid; volume->v_vid = htons(volume->v_vid); + volume->v_vfs_ea = AFPVOL_EA_AD; /* handle options */ if (options) { @@ -714,6 +720,9 @@ static int creatvol(AFPObj *obj, struct passwd *pwd, /* shift in some flags */ volume->v_flags = options[VOLOPT_FLAGS].i_value; + + if (options[VOLOPT_EA_VFS].i_value != AFPVOL_EA_AD) + volume->v_vfs_ea = options[VOLOPT_EA_VFS].i_value; volume->v_ad_options = 0; if ((volume->v_flags & AFPVOL_NODEV)) @@ -1463,6 +1472,7 @@ int *buflen; ashort |= VOLPBIT_ATTR_RO; } ashort |= VOLPBIT_ATTR_CATSEARCH; + ashort |= VOLPBIT_ATTR_EXT_ATTRS; if (afp_version >= 30) { ashort |= VOLPBIT_ATTR_UTF8; if (vol->v_flags & AFPVOL_UNIX_PRIV) @@ -1471,10 +1481,8 @@ int *buflen; ashort |= VOLPBIT_ATTR_TM; } if (afp_version >= 32) { - if (vol->v_flags & AFPVOL_EXT_ATTRS) - ashort |= VOLPBIT_ATTR_EXT_ATTRS; if (vol->v_flags & AFPVOL_ACLS) - ashort |= VOLPBIT_ATTR_ACLS; + ashort |= VOLPBIT_ATTR_ACLS; } ashort = htons(ashort); memcpy(data, &ashort, sizeof( ashort )); diff --git a/etc/afpd/volume.h b/etc/afpd/volume.h index b579362b..cc3dfdc9 100644 --- a/etc/afpd/volume.h +++ b/etc/afpd/volume.h @@ -1,5 +1,5 @@ /* - * $Id: volume.h,v 1.33 2009-09-11 09:14:16 franklahm Exp $ + * $Id: volume.h,v 1.34 2009-10-02 09:32:40 franklahm Exp $ * * Copyright (c) 1990,1994 Regents of The University of Michigan. * All Rights Reserved. See COPYRIGHT. @@ -12,200 +12,15 @@ #include #include -#include "atalk/unicode.h" -#include "globals.h" -#include "afp_vfs.h" -#include "hash.h" - -#define AFPVOL_U8MNAMELEN 255 /* AFP3 sepc */ -#define AFPVOL_MACNAMELEN 27 /* AFP2 spec */ - +#include #include +#include -struct vol { - struct vol *v_next; - char *v_localname; /* as defined in AppleVolumes.default */ - ucs2_t *v_u8mname; /* converted to utf8-mac in ucs2 */ - ucs2_t *v_macname; /* mangled to legacy longname in ucs2 */ - ucs2_t *v_name; /* either v_u8mname or v_macname */ - char *v_path; - - struct dir *v_dir, *v_root; - hash_t *v_hash; - int v_flags; -#ifdef __svr4__ - int v_qfd; -#endif /*__svr4__*/ - char *v_gvs; - time_t v_mtime; - time_t v_ctime; /* volume creation date, not unix ctime */ - - u_int16_t v_vid; - void *v_nfsclient; - int v_nfs; - - int v_casefold; - size_t max_filename; - - char *v_password; - char *v_veto; - - char *v_cnidscheme; - char *v_dbpath; - dev_t v_dev; /* Unix volume device */ - struct _cnid_db *v_cdb; - char v_stamp[ADEDLEN_PRIVSYN]; - mode_t v_umask; - mode_t v_perm; /* default permission value OR with requested perm*/ - mode_t v_dperm; /* default directories permission value OR with requested perm*/ - mode_t v_fperm; /* default files permission value OR with requested perm*/ - -#ifdef FORCE_UIDGID - char *v_forceuid; - char *v_forcegid; -#endif - - char *v_volcodepage; - charset_t v_volcharset; - struct charset_functions *v_vol; - char *v_maccodepage; - charset_t v_maccharset; - struct charset_functions *v_mac; - - int v_deleted; /* volume open but deleted in new config file */ - int v_hide; /* new volume wait until old volume is closed */ - int v_new; /* volume deleted but there's a new one with the same name */ - int v_adouble; /* default adouble format */ - int v_ad_options; /* adouble option NODEV, NOCACHE, etc.. */ - - char *v_root_preexec; - char *v_preexec; - - char *v_root_postexec; - char *v_postexec; - - int v_root_preexec_close; - int v_preexec_close; - - /* adouble indirection */ - struct vfs_ops *vfs; - int (*validupath)(const struct vol *, const char *); - char *(*ad_path)(const char *, int); -}; - -#ifdef NO_LARGE_VOL_SUPPORT -typedef u_int32_t VolSpace; -#else /* NO_LARGE_VOL_SUPPORT */ -typedef u_int64_t VolSpace; -#endif /* NO_LARGE_VOL_SUPPORT */ - -#define AFPVOL_OPEN (1<<0) - -/* flags for AFS and quota 0xxx0 */ -#define AFPVOL_GVSMASK (7<<2) -#define AFPVOL_NONE (0<<2) -#define AFPVOL_AFSGVS (1<<2) -#define AFPVOL_USTATFS (2<<2) -#define AFPVOL_UQUOTA (4<<2) - -/* - Flags that alter volume behaviour. - Keep in sync with include/atalk/volinfo.h and libatalk/util/volinfo.c -*/ -#define AFPVOL_A2VOL (1 << 5) /* prodos volume */ -#define AFPVOL_CRLF (1 << 6) /* cr/lf translation */ -#define AFPVOL_NOADOUBLE (1 << 7) /* don't create .AppleDouble by default */ -#define AFPVOL_RO (1 << 8) /* read-only volume */ -#define AFPVOL_MSWINDOWS (1 << 9) /* deal with ms-windows yuckiness. -this is going away. */ -#define AFPVOL_NOHEX (1 << 10) /* don't do :hex translation */ -#define AFPVOL_USEDOTS (1 << 11) /* use real dots */ -#define AFPVOL_LIMITSIZE (1 << 12) /* limit size for older macs */ -#define AFPVOL_MAPASCII (1 << 13) /* map the ascii range as well */ -#define AFPVOL_DROPBOX (1 << 14) /* dropkludge dropbox support */ -#define AFPVOL_NOFILEID (1 << 15) /* don't advertise createid resolveid and deleteid calls */ -#define AFPVOL_NOSTAT (1 << 16) /* advertise the volume even if we can't stat() it - * maybe because it will be mounted later in preexec */ -#define AFPVOL_UNIX_PRIV (1 << 17) /* support unix privileges */ -#define AFPVOL_NODEV (1 << 18) /* always use 0 for device number in cnid calls - * help if device number is notconsistent across reboot - * NOTE symlink to a different device will return an ACCESS error - */ -#define AFPVOL_CASEINSEN (1 << 19) /* volume is case insensitive */ -#define AFPVOL_EILSEQ (1 << 20) /* encode illegal sequence 'asis' UCS2, ex "\217-", which is not - a valid SHIFT-JIS char, is encoded as U\217 -*/ - -#define AFPVOL_CACHE (1 << 21) /* Use adouble v2 CNID caching. Default: yes */ -#define AFPVOL_INV_DOTS (1 << 22) /* dots files are invisible */ -#define AFPVOL_EXT_ATTRS (1 << 23) /* Volume supports Extended Attributes */ -#define AFPVOL_TM (1 << 24) /* Supports TimeMachine */ -#define AFPVOL_ACLS (1 << 25) /* Volume supports ACLS */ - -/* FPGetSrvrParms options */ -#define AFPSRVR_CONFIGINFO (1 << 0) -#define AFPSRVR_PASSWD (1 << 7) - -/* handle casefolding */ -#define AFPVOL_MTOUUPPER (1 << 0) -#define AFPVOL_MTOULOWER (1 << 1) -#define AFPVOL_UTOMUPPER (1 << 2) -#define AFPVOL_UTOMLOWER (1 << 3) -#define AFPVOL_UMLOWER (AFPVOL_MTOULOWER | AFPVOL_UTOMLOWER) -#define AFPVOL_UMUPPER (AFPVOL_MTOUUPPER | AFPVOL_UTOMUPPER) -#define AFPVOL_UUPPERMLOWER (AFPVOL_MTOUUPPER | AFPVOL_UTOMLOWER) -#define AFPVOL_ULOWERMUPPER (AFPVOL_MTOULOWER | AFPVOL_UTOMUPPER) - -#define MSWINDOWS_BADCHARS ":\t\\/<>*?|\"" - -int wincheck(const struct vol *vol, const char *path); - -#define AFPVOLSIG_FLAT 0x0001 /* flat fs */ -#define AFPVOLSIG_FIX 0x0002 /* fixed ids */ -#define AFPVOLSIG_VAR 0x0003 /* variable ids */ -#define AFPVOLSIG_DEFAULT AFPVOLSIG_FIX - -/* volume attributes */ -#define VOLPBIT_ATTR_RO (1 << 0) -#define VOLPBIT_ATTR_PASSWD (1 << 1) -#define VOLPBIT_ATTR_FILEID (1 << 2) -#define VOLPBIT_ATTR_CATSEARCH (1 << 3) -#define VOLPBIT_ATTR_BLANKACCESS (1 << 4) -#define VOLPBIT_ATTR_UNIXPRIV (1 << 5) -#define VOLPBIT_ATTR_UTF8 (1 << 6) -#define VOLPBIT_ATTR_NONETUID (1 << 7) -#define VOLPBIT_ATTR_EXT_ATTRS (1 << 10) -#define VOLPBIT_ATTR_ACLS (1 << 11) -#define VOLPBIT_ATTR_TM (1 << 13) - -#define VOLPBIT_ATTR 0 -#define VOLPBIT_SIG 1 -#define VOLPBIT_CDATE 2 -#define VOLPBIT_MDATE 3 -#define VOLPBIT_BDATE 4 -#define VOLPBIT_VID 5 -#define VOLPBIT_BFREE 6 -#define VOLPBIT_BTOTAL 7 -#define VOLPBIT_NAME 8 -/* handle > 4GB volumes */ -#define VOLPBIT_XBFREE 9 -#define VOLPBIT_XBTOTAL 10 -#define VOLPBIT_BSIZE 11 /* block size */ - - -#define vol_noadouble(vol) (((vol)->v_flags & AFPVOL_NOADOUBLE) ? \ - ADFLAGS_NOADOUBLE : 0) -#ifdef AFP3x -#define utf8_encoding() (afp_version >= 30) -#else -#define utf8_encoding() (0) +#include "globals.h" +#if 0 +#include "hash.h" #endif -#define vol_nodev(vol) (((vol)->v_flags & AFPVOL_NODEV) ? 1 : 0) - -#define vol_unix_priv(vol) (afp_version >= 30 && ((vol)->v_flags & AFPVOL_UNIX_PRIV)) - -#define vol_inv_dots(vol) (((vol)->v_flags & AFPVOL_INV_DOTS) ? 1 : 0) - extern struct vol *getvolbyvid __P((const u_int16_t)); extern int ustatfs_getvolspace __P((const struct vol *, VolSpace *, VolSpace *, diff --git a/include/atalk/Makefile.am b/include/atalk/Makefile.am index 77a7e898..f7af5585 100644 --- a/include/atalk/Makefile.am +++ b/include/atalk/Makefile.am @@ -2,10 +2,10 @@ atalkincludedir = $(includedir)/atalk atalkinclude_HEADERS = \ - adouble.h aep.h afp.h asp.h atp.h boolean.h \ + adouble.h vfs.h aep.h afp.h asp.h atp.h boolean.h \ cnid.h compat.h ddp.h dsi.h ldapconfig.h list.h logger.h \ nbp.h netddp.h pap.h paths.h rtmp.h server_child.h \ server_ipc.h tdb.h uam.h unicode.h util.h uuid.h volinfo.h \ - zip.h + zip.h ea.h noinst_HEADERS = cnid_dbd_private.h diff --git a/include/atalk/adouble.h b/include/atalk/adouble.h index f5894e7d..580bff6a 100644 --- a/include/atalk/adouble.h +++ b/include/atalk/adouble.h @@ -1,5 +1,5 @@ /* - * $Id: adouble.h,v 1.42 2009-09-14 14:11:45 franklahm Exp $ + * $Id: adouble.h,v 1.43 2009-10-02 09:32:40 franklahm Exp $ * Copyright (c) 1990,1991 Regents of The University of Michigan. * All Rights Reserved. * @@ -131,7 +131,6 @@ #define AD_APPLESINGLE_MAGIC 0x00051600 #define AD_APPLEDOUBLE_MAGIC 0x00051607 #define AD_MAGIC AD_APPLEDOUBLE_MAGIC - #define SFM_MAGIC 0x00504641 /* sizes of relevant entry bits */ @@ -552,4 +551,13 @@ extern ssize_t ad_writefile __P((struct adouble *, const int, #endif /* HAVE_SENDFILE_WRITE */ #endif /* 0 */ +/* ad_unix.c */ +extern int netatalk_unlink(const char *name); +extern char *fullpathname(const char *); +extern int netatalk_rmdir(const char *name); +extern int setfilmode(const char *, mode_t, struct stat *, mode_t); +extern int dir_rx_set(mode_t mode); +extern int stickydirmode(const char *name, const mode_t mode, const int dropbox, const mode_t v_umask); +extern int unix_rename(const char *oldpath, const char *newpath); + #endif /* _ATALK_ADOUBLE_H */ diff --git a/include/atalk/afp.h b/include/atalk/afp.h index fff1f41a..aecf9543 100644 --- a/include/atalk/afp.h +++ b/include/atalk/afp.h @@ -199,6 +199,7 @@ typedef enum { /* version 3.1 */ #define AFP_ENUMERATE_EXT2 68 +#define AFP_SPOTLIGHT_PRIVATE 76 #define AFP_SYNCDIR 78 #define AFP_SYNCFORK 79 #define AFP_ZZZ 122 @@ -211,6 +212,5 @@ typedef enum { #define AFP_GETACL 73 #define AFP_SETACL 74 #define AFP_ACCESS 75 -#define AFP_SPOTLIGHT_PRIVATE 76 #endif diff --git a/include/atalk/directory.h b/include/atalk/directory.h new file mode 100644 index 00000000..d02b4f22 --- /dev/null +++ b/include/atalk/directory.h @@ -0,0 +1,87 @@ +/* + * $Id: directory.h,v 1.1 2009-10-02 09:32:40 franklahm Exp $ + * + * Copyright (c) 1990,1991 Regents of The University of Michigan. + * All Rights Reserved. + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appears in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation, and that the name of The University + * of Michigan not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. This software is supplied as is without expressed or + * implied warranties of any kind. + * + * Research Systems Unix Group + * The University of Michigan + * c/o Mike Clark + * 535 W. William Street + * Ann Arbor, Michigan + * +1-313-763-0525 + * netatalk@itd.umich.edu + */ + +#ifndef ATALK_DIRECTORY_H +#define ATALK_DIRECTORY_H 1 + +#include +#include +#include +#include + +#include +#include + +/* setgid directories */ +#ifndef DIRBITS +# ifdef AFS +# define DIRBITS 0 +# else /* AFS */ +# define DIRBITS S_ISGID +# endif /* AFS */ +#endif /* DIRBITS */ + +/* the did tree is now a red-black tree while the parent/child + * tree is a circular doubly-linked list. how exciting. */ +struct dir { + struct dir *d_left, *d_right, *d_back; /* for red-black tree */ + int d_color; + struct dir *d_parent, *d_child; /* parent-child */ + struct dir *d_prev, *d_next; /* siblings */ + void *d_ofork; /* oforks using this directory. */ + u_int32_t d_did; + int d_flags; + + time_t ctime; /* inode ctime */ + u_int32_t offcnt; /* offspring count */ + + char *d_m_name; /* mac name */ + char *d_u_name; /* unix name */ + ucs2_t *d_m_name_ucs2; /* mac name as UCS2 */ +}; + +struct path { + int m_type; /* mac name type (long name, unicode */ + char *m_name; /* mac name */ + char *u_name; /* unix name */ + cnid_t id; /* file id (only for getmetadata) */ + struct dir *d_dir; /* */ + int st_valid; /* does st_errno and st set */ + int st_errno; + struct stat st; +}; + +static inline int path_isadir(struct path *o_path) +{ + return o_path->d_dir != NULL; +#if 0 + return o_path->m_name == '\0' || /* we are in a it */ + !o_path->st_valid || /* in cache but we can't chdir in it */ + (!o_path->st_errno && S_ISDIR(o_path->st.st_mode)); /* not in cache an can't chdir */ +#endif +} + + +#endif /* ATALK_DIRECTORY_H */ diff --git a/include/atalk/ea.h b/include/atalk/ea.h new file mode 100644 index 00000000..3509745c --- /dev/null +++ b/include/atalk/ea.h @@ -0,0 +1,110 @@ +/* + $Id: ea.h,v 1.1 2009-10-02 09:32:40 franklahm Exp $ + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +#ifndef ATALK_EA_H +#define ATALK_EA_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* + * This seems to be the current limit fo HFS+, we arbitrarily force that + * which also safes us from buffer overflows + */ +#define MAX_EA_SIZE 3802 + +/* + * At time of writing the 10.5.6 client adds 8 bytes to the + * length of the EA that we send him +*/ +#define MAX_REPLY_EXTRA_BYTES 8 + +/* + * Library user must provide a static buffer of size ATTRNAMEBUFSIZ. + * It's used when listing EAs as intermediate buffer. For afpd it's + * defined in extattrs.c. + */ +#define ATTRNAMEBUFSIZ 4096 + +enum { + kXAttrNoFollow = 0x1, + kXAttrCreate = 0x2, + kXAttrReplace = 0x4 +}; + + +#define EA_MAGIC 0x61644541 /* "adEA" */ +#define EA_VERSION1 0x01 +#define EA_VERSION EA_VERSION1 + +typedef enum { + /* ea_open flags */ + EA_CREATE = (1<<1), /* create if not existing on ea_open */ + EA_RDONLY = (1<<2), /* open read only */ + EA_RDWR = (1<<3), /* open read/write */ + /* ea_open internal flags */ + EA_DIR = (1<<4) /* ea header file is for a dir, ea_open adds it as appropiate */ +} eaflags_t; + +#define EA_MAGIC_OFF 0 +#define EA_MAGIC_LEN 4 +#define EA_VERSION_OFF (EA_MAGIC_OFF + EA_MAGIC_LEN) +#define EA_VERSION_LEN 2 +#define EA_COUNT_OFF (EA_VERSION_OFF + EA_VERSION_LEN) +#define EA_COUNT_LEN 2 +#define EA_HEADER_SIZE (EA_MAGIC_LEN + EA_VERSION_LEN + EA_COUNT_LEN) + +/* + * structs describing the layout of the Extended Attributes bookkeeping file. + * This isn't really an AppleDouble structure, it's just a binary blob that + * lives in our .AppleDouble directory too. + */ + +struct ea_entry { + size_t ea_namelen; /* len of ea_name without terminating 0 ie. strlen(ea_name)*/ + size_t ea_size; /* size of EA*/ + char *ea_name; /* name of the EA */ +}; + +/* We read the on-disk data into *ea_data and parse it into this*/ +struct ea { + const struct vol *vol; /* vol handle, ea_close needs it */ + char *filename; /* name of file, needed by ea_close too */ + unsigned int ea_count; /* number of EAs in ea_entries array */ + struct ea_entry (*ea_entries)[]; /* malloced and realloced as needed by ea_count*/ + int ea_fd; /* open fd for ea_data */ + eaflags_t ea_flags; /* flags */ + size_t ea_size; /* size of header file = size of ea_data buffer */ + char *ea_data; /* pointer to buffer into that we actually * + * read the disc file into */ +}; + +/* On-disk format, just for reference ! */ +#if 0 +struct ea_entry_ondisk { + uint32_t ea_size; + char ea_name[]; /* zero terminated string */ +}; + +struct ea_ondisk { + u_int32_t ea_magic; + u_int16_t ea_version; + u_int16_t ea_count; + struct ea_entry_ondisk ea_entries[ea_count]; +}; +#endif /* 0 */ + +#endif /* ATALK_EA_H */ diff --git a/include/atalk/hash.h b/include/atalk/hash.h new file mode 100644 index 00000000..9195a4b5 --- /dev/null +++ b/include/atalk/hash.h @@ -0,0 +1,179 @@ +/* + * Hash Table Data Type + * Copyright (C) 1997 Kaz Kylheku + * + * Free Software License: + * + * All rights are reserved by the author, with the following exceptions: + * Permission is granted to freely reproduce and distribute this software, + * possibly in exchange for a fee, provided that this copyright notice appears + * intact. Permission is also granted to adapt this software to produce + * derivative works, as long as the modified versions carry this copyright + * notice and additional notices stating that the work has been modified. + * This source code may be translated into executable form and incorporated + * into proprietary software; there is no requirement for such software to + * contain a copyright notice related to this source. + * + * $Id: hash.h,v 1.1 2009-10-02 09:32:40 franklahm Exp $ + * $Name: $ + */ + +#ifndef ATALK_HASH_H +#define ATALK_HASH_H + +#include + +typedef unsigned long hashcount_t; +#define HASHCOUNT_T_MAX ULONG_MAX + +typedef unsigned long hash_val_t; +#define HASH_VAL_T_MAX ULONG_MAX + +extern int hash_val_t_bit; + +#ifndef HASH_VAL_T_BIT +#define HASH_VAL_T_BIT ((int) hash_val_t_bit) +#endif + +/* + * Hash chain node structure. + * Notes: + * 1. This preprocessing directive is for debugging purposes. The effect is + * that if the preprocessor symbol KAZLIB_OPAQUE_DEBUG is defined prior to the + * inclusion of this header, then the structure shall be declared as having + * the single member int __OPAQUE__. This way, any attempts by the + * client code to violate the principles of information hiding (by accessing + * the structure directly) can be diagnosed at translation time. However, + * note the resulting compiled unit is not suitable for linking. + * 2. This is a pointer to the next node in the chain. In the last node of a + * chain, this pointer is null. + * 3. The key is a pointer to some user supplied data that contains a unique + * identifier for each hash node in a given table. The interpretation of + * the data is up to the user. When creating or initializing a hash table, + * the user must supply a pointer to a function for comparing two keys, + * and a pointer to a function for hashing a key into a numeric value. + * 4. The value is a user-supplied pointer to void which may refer to + * any data object. It is not interpreted in any way by the hashing + * module. + * 5. The hashed key is stored in each node so that we don't have to rehash + * each key when the table must grow or shrink. + */ + +typedef struct hnode_t { + #if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) /* 1 */ + struct hnode_t *hash_next; /* 2 */ + const void *hash_key; /* 3 */ + void *hash_data; /* 4 */ + hash_val_t hash_hkey; /* 5 */ + #else + int hash_dummy; + #endif +} hnode_t; + +/* + * The comparison function pointer type. A comparison function takes two keys + * and produces a value of -1 if the left key is less than the right key, a + * value of 0 if the keys are equal, and a value of 1 if the left key is + * greater than the right key. + */ + +typedef int (*hash_comp_t)(const void *, const void *); + +/* + * The hashing function performs some computation on a key and produces an + * integral value of type hash_val_t based on that key. For best results, the + * function should have a good randomness properties in *all* significant bits + * over the set of keys that are being inserted into a given hash table. In + * particular, the most significant bits of hash_val_t are most significant to + * the hash module. Only as the hash table expands are less significant bits + * examined. Thus a function that has good distribution in its upper bits but + * not lower is preferrable to one that has poor distribution in the upper bits + * but not the lower ones. + */ + +typedef hash_val_t (*hash_fun_t)(const void *); + +/* + * allocator functions + */ + +typedef hnode_t *(*hnode_alloc_t)(void *); +typedef void (*hnode_free_t)(hnode_t *, void *); + +/* + * This is the hash table control structure. It keeps track of information + * about a hash table, as well as the hash table itself. + * Notes: + * 1. Pointer to the hash table proper. The table is an array of pointers to + * hash nodes (of type hnode_t). If the table is empty, every element of + * this table is a null pointer. A non-null entry points to the first + * element of a chain of nodes. + * 2. This member keeps track of the size of the hash table---that is, the + * number of chain pointers. + * 3. The count member maintains the number of elements that are presently + * in the hash table. + * 4. The maximum count is the greatest number of nodes that can populate this + * table. If the table contains this many nodes, no more can be inserted, + * and the hash_isfull() function returns true. + * 5. The high mark is a population threshold, measured as a number of nodes, + * which, if exceeded, will trigger a table expansion. Only dynamic hash + * tables are subject to this expansion. + * 6. The low mark is a minimum population threshold, measured as a number of + * nodes. If the table population drops below this value, a table shrinkage + * will occur. Only dynamic tables are subject to this reduction. No table + * will shrink beneath a certain absolute minimum number of nodes. + * 7. This is the a pointer to the hash table's comparison function. The + * function is set once at initialization or creation time. + * 8. Pointer to the table's hashing function, set once at creation or + * initialization time. + * 9. The current hash table mask. If the size of the hash table is 2^N, + * this value has its low N bits set to 1, and the others clear. It is used + * to select bits from the result of the hashing function to compute an + * index into the table. + * 10. A flag which indicates whether the table is to be dynamically resized. It + * is set to 1 in dynamically allocated tables, 0 in tables that are + * statically allocated. + */ + +typedef struct hash_t { + #if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) + struct hnode_t **hash_table; /* 1 */ + hashcount_t hash_nchains; /* 2 */ + hashcount_t hash_nodecount; /* 3 */ + hashcount_t hash_maxcount; /* 4 */ + hashcount_t hash_highmark; /* 5 */ + hashcount_t hash_lowmark; /* 6 */ + hash_comp_t hash_compare; /* 7 */ + hash_fun_t hash_function; /* 8 */ + hnode_alloc_t hash_allocnode; + hnode_free_t hash_freenode; + void *hash_context; + hash_val_t hash_mask; /* 9 */ + int hash_dynamic; /* 10 */ + #else + int hash_dummy; + #endif +} hash_t; + +/* + * Hash scanner structure, used for traversals of the data structure. + * Notes: + * 1. Pointer to the hash table that is being traversed. + * 2. Reference to the current chain in the table being traversed (the chain + * that contains the next node that shall be retrieved). + * 3. Pointer to the node that will be retrieved by the subsequent call to + * hash_scan_next(). + */ + +typedef struct hscan_t { + #if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) + hash_t *hash_table; /* 1 */ + hash_val_t hash_chain; /* 2 */ + hnode_t *hash_next; /* 3 */ + #else + int hash_dummy; + #endif +} hscan_t; + + +#endif /* ATALK_HASH_H */ diff --git a/include/atalk/util.h b/include/atalk/util.h index bb5bed2b..4811b47d 100644 --- a/include/atalk/util.h +++ b/include/atalk/util.h @@ -1,5 +1,5 @@ /* - * $Id: util.h,v 1.9 2009-04-28 13:01:24 franklahm Exp $ + * $Id: util.h,v 1.10 2009-10-02 09:32:40 franklahm Exp $ */ #ifndef _ATALK_UTIL_H @@ -106,4 +106,54 @@ extern int loadvolinfo __P((char *path, struct volinfo *vol)); extern int vol_load_charsets __P(( struct volinfo *vol)); #endif /* 0 */ +/* + * Function: lock_reg + * + * Purpose: lock a file with fctnl + * + * Arguments: + * + * fd (r) File descriptor + * cmd (r) cmd to fcntl, only F_SETLK is usable here + * type (r) F_RDLCK, F_WRLCK, F_UNLCK + * offset (r) byte offset relative to l_whence + * whence (r) SEEK_SET, SEEK_CUR, SEEK_END + * len (r) no. of bytes (0 means to EOF) + * + * Returns: 0 on success, -1 on failure + * fcntl return value and errno + * + * Effects: + * + * Function called by macros to ease locking. + */ +extern int lock_reg(int fd, int cmd, int type, off_t offest, int whence, off_t len); + +/* + * Macros: read_lock, write_lock, un_lock + * + * Purpose: lock and unlock files + * + * Arguments: + * + * fd (r) File descriptor + * offset (r) byte offset relative to l_whence + * whence (r) SEEK_SET, SEEK_CUR, SEEK_END + * len (r) no. of bytes (0 means to EOF) + * + * Returns: 0 on success, -1 on failure + * fcntl return value and errno + * + * Effects: + * + * Nice locking macros. + */ + +#define read_lock(fd, offset, whence, len) \ + lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len)) +#define write_lock(fd, offset, whence, len) \ + lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len)) +#define unlock(fd, offset, whence, len) \ + lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len)) + #endif diff --git a/include/atalk/vfs.h b/include/atalk/vfs.h new file mode 100644 index 00000000..bca16be1 --- /dev/null +++ b/include/atalk/vfs.h @@ -0,0 +1,90 @@ +/* + Copyright (c) 2004 Didier Gautheron + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + vfs layer for afp +*/ + +#ifndef ATALK_VFS_H +#define ATALK_VFS_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#ifdef HAVE_NFSv4_ACLS +#include +#endif + +#include +#include + +struct vfs_ops { + /* low level adouble fn */ + char *(*ad_path)(const char *, int); + + /* */ + int (*validupath)(const struct vol *, const char *); + int (*rf_chown)(const struct vol *, const char *path, uid_t owner, gid_t group); + int (*rf_renamedir)(const struct vol *, const char *oldpath, const char *newpath); + int (*rf_deletecurdir)(const struct vol *); + int (*rf_setfilmode)(const struct vol *, const char * name, mode_t mode, struct stat *st); + int (*rf_setdirmode)(const struct vol *, const char * name, mode_t mode, struct stat *st); + int (*rf_setdirunixmode)(const struct vol *, const char * name, mode_t mode, struct stat *st); + + int (*rf_setdirowner)(const struct vol *, const char *path, uid_t owner, gid_t group); + + int (*rf_deletefile)(const struct vol *, const char * ); + int (*rf_renamefile)(const struct vol *, const char *oldpath, const char *newpath); +#ifdef HAVE_NFSv4_ACLS + int (*rf_acl)(const struct vol *, const char *path, int cmd, int count, ace_t *aces); + int (*rf_remove_acl)(const struct vol *, const char *path, int dir); +#endif + + /* Extended Attributes */ + int (*get_easize)(const struct vol * restrict, char * restrict rbuf, int * restrict rbuflen, const char * restrict uname, int oflag, const char * restrict attruname); + int (*get_eacontent)(const struct vol * restrict , char * restrict rbuf, int * restrict rbuflen, const char * restrict uname, int oflag, const char * restrict attruname, int maxreply); + int (*list_eas)(const struct vol * restrict , char * restrict attrnamebuf, int * restrict buflen, const char * restrict uname, int oflag); + int (*set_ea)(const struct vol * restrict , const char * restrict uname, const char * restrict attruname, const char * restrict ibuf, size_t attrsize, int oflag); + int (*remove_ea)(const struct vol * restrict , const char * restrict uname, const char * restrict attruname, int oflag); +}; + +extern void initvol_vfs(struct vol * restrict vol); + +/* VFS inderected funcs ... : */ +/* ...default adouble EAs */ +extern int get_easize(const struct vol * restrict vol, char * restrict rbuf, int * restrict rbuflen, const char * restrict uname, int oflag, const char * restrict attruname); +extern 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); +extern int list_eas(const struct vol * restrict vol, char * restrict attrnamebuf, int * restrict buflen, const char * restrict uname, int oflag); +extern 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); +extern int remove_ea(const struct vol * restrict vol, const char * restrict uname, const char * restrict attruname, int oflag); + +/* ... Solaris native EAs */ +#ifdef HAVE_SOLARIS_EAS +extern int sol_get_easize(const struct vol * restrict vol, char * restrict rbuf, int * restrict rbuflen, const char * restrict uname, int oflag, const char * restrict attruname); +extern 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); +extern int sol_list_eas(const struct vol * restrict vol, char * restrict attrnamebuf, int * restrict buflen, const char * restrict uname, int oflag); +extern int sol_set_ea(const struct vol * restrict vol, const char * restrict uname, const char * restrict attruname, const char * restrict ibuf,size_t attrsize, int oflag); +extern int sol_remove_ea(const struct vol * restrict vol, const char * restrict uname, const char * restrict attruname, int oflag); +#endif /* HAVE_SOLARIS_EAS */ + +/* Solaris NFSv4 ACL stuff */ +#ifdef HAVE_NFSv4_ACLS +extern int get_nfsv4_acl(const char *name, ace_t **retAces); +extern int remove_acl(const char *name); +#endif /* HAVE_NFSv4_ACLS */ + +#endif /* ATALK_VFS_H */ diff --git a/include/atalk/volinfo.h b/include/atalk/volinfo.h index 73bc29f5..541f2873 100644 --- a/include/atalk/volinfo.h +++ b/include/atalk/volinfo.h @@ -1,5 +1,5 @@ /* - * $Id: volinfo.h,v 1.7 2009-09-11 09:14:16 franklahm Exp $ + * $Id: volinfo.h,v 1.8 2009-10-02 09:32:41 franklahm Exp $ */ #ifndef _ATALK_VOLINFO_H @@ -32,7 +32,6 @@ a valid SHIFT-JIS char, is encoded as U\217 -*/ #define AFPVOL_CACHE (1 << 21) /* Use adouble v2 CNID caching, default don't use it */ #define AFPVOL_INV_DOTS (1 << 22) /* dots files are invisible */ -#define AFPVOL_EXT_ATTRS (1 << 23) /* Volume supports Extended Attributes */ #define AFPVOL_TM (1 << 24) /* Supports TimeMachine */ #define AFPVOL_ACLS (1 << 25) /* Volume supports ACLS */ diff --git a/include/atalk/volume.h b/include/atalk/volume.h new file mode 100644 index 00000000..64c5e962 --- /dev/null +++ b/include/atalk/volume.h @@ -0,0 +1,209 @@ +/* + * $Id: volume.h,v 1.1 2009-10-02 09:32:41 franklahm Exp $ + * + * Copyright (c) 1990,1994 Regents of The University of Michigan. + * All Rights Reserved. See COPYRIGHT. + */ + +#ifndef ATALK_VOLUME_H +#define ATALK_VOLUME_H 1 + +#include +#include +#include + +#include +#include +#include + +#define AFPVOL_U8MNAMELEN 255 /* AFP3 sepc */ +#define AFPVOL_MACNAMELEN 27 /* AFP2 spec */ + +struct vol { + struct vol *v_next; + char *v_localname; /* as defined in AppleVolumes.default */ + ucs2_t *v_u8mname; /* converted to utf8-mac in ucs2 */ + ucs2_t *v_macname; /* mangled to legacy longname in ucs2 */ + ucs2_t *v_name; /* either v_u8mname or v_macname */ + char *v_path; + + struct dir *v_dir, *v_root; + hash_t *v_hash; + int v_flags; +#ifdef __svr4__ + int v_qfd; +#endif /*__svr4__*/ + char *v_gvs; + time_t v_mtime; + time_t v_ctime; /* volume creation date, not unix ctime */ + + u_int16_t v_vid; + void *v_nfsclient; + int v_nfs; + + int v_casefold; + size_t max_filename; + + char *v_password; + char *v_veto; + + char *v_cnidscheme; + char *v_dbpath; + dev_t v_dev; /* Unix volume device */ + struct _cnid_db *v_cdb; + char v_stamp[ADEDLEN_PRIVSYN]; + mode_t v_umask; + mode_t v_perm; /* default permission value OR with requested perm*/ + mode_t v_dperm; /* default directories permission value OR with requested perm*/ + mode_t v_fperm; /* default files permission value OR with requested perm*/ + +#ifdef FORCE_UIDGID + char *v_forceuid; + char *v_forcegid; +#endif + + char *v_volcodepage; + charset_t v_volcharset; + struct charset_functions *v_vol; + char *v_maccodepage; + charset_t v_maccharset; + struct charset_functions *v_mac; + + int v_deleted; /* volume open but deleted in new config file */ + int v_hide; /* new volume wait until old volume is closed */ + int v_new; /* volume deleted but there's a new one with the same name */ + int v_adouble; /* default adouble format */ + int v_ad_options; /* adouble option NODEV, NOCACHE, etc.. */ + + char *v_root_preexec; + char *v_preexec; + + char *v_root_postexec; + char *v_postexec; + + int v_root_preexec_close; + int v_preexec_close; + + /* adouble indirection */ + struct vfs_ops *vfs; + int v_vfs_ea; /* The AFPVOL_EA_xx flag */ + int (*validupath)(const struct vol *, const char *); + char *(*ad_path)(const char *, int); +}; + +#ifdef NO_LARGE_VOL_SUPPORT +typedef u_int32_t VolSpace; +#else /* NO_LARGE_VOL_SUPPORT */ +typedef u_int64_t VolSpace; +#endif /* NO_LARGE_VOL_SUPPORT */ + +#define AFPVOL_OPEN (1<<0) + +/* flags for AFS and quota 0xxx0 */ +#define AFPVOL_GVSMASK (7<<2) +#define AFPVOL_NONE (0<<2) +#define AFPVOL_AFSGVS (1<<2) +#define AFPVOL_USTATFS (2<<2) +#define AFPVOL_UQUOTA (4<<2) + +/* + Flags that alter volume behaviour. + Keep in sync with include/atalk/volinfo.h and libatalk/util/volinfo.c +*/ +#define AFPVOL_A2VOL (1 << 5) /* prodos volume */ +#define AFPVOL_CRLF (1 << 6) /* cr/lf translation */ +#define AFPVOL_NOADOUBLE (1 << 7) /* don't create .AppleDouble by default */ +#define AFPVOL_RO (1 << 8) /* read-only volume */ +#define AFPVOL_MSWINDOWS (1 << 9) /* deal with ms-windows yuckiness. +this is going away. */ +#define AFPVOL_NOHEX (1 << 10) /* don't do :hex translation */ +#define AFPVOL_USEDOTS (1 << 11) /* use real dots */ +#define AFPVOL_LIMITSIZE (1 << 12) /* limit size for older macs */ +#define AFPVOL_MAPASCII (1 << 13) /* map the ascii range as well */ +#define AFPVOL_DROPBOX (1 << 14) /* dropkludge dropbox support */ +#define AFPVOL_NOFILEID (1 << 15) /* don't advertise createid resolveid and deleteid calls */ +#define AFPVOL_NOSTAT (1 << 16) /* advertise the volume even if we can't stat() it + * maybe because it will be mounted later in preexec */ +#define AFPVOL_UNIX_PRIV (1 << 17) /* support unix privileges */ +#define AFPVOL_NODEV (1 << 18) /* always use 0 for device number in cnid calls + * help if device number is notconsistent across reboot + * NOTE symlink to a different device will return an ACCESS error + */ +#define AFPVOL_CASEINSEN (1 << 19) /* volume is case insensitive */ +#define AFPVOL_EILSEQ (1 << 20) /* encode illegal sequence 'asis' UCS2, ex "\217-", which is not + a valid SHIFT-JIS char, is encoded as U\217 -*/ + +#define AFPVOL_CACHE (1 << 21) /* Use adouble v2 CNID caching. Default: yes */ +#define AFPVOL_INV_DOTS (1 << 22) /* dots files are invisible */ +#define AFPVOL_TM (1 << 24) /* Supports TimeMachine */ +#define AFPVOL_ACLS (1 << 25) /* Volume supports ACLS */ + +/* Extended Attributes vfs indirection */ +#define AFPVOL_EA_AD 0 /* Store them in adouble files. Default. */ +#define AFPVOL_EA_SOLARIS 1 /* Store them in native Solaris EAs */ + +/* FPGetSrvrParms options */ +#define AFPSRVR_CONFIGINFO (1 << 0) +#define AFPSRVR_PASSWD (1 << 7) + +/* handle casefolding */ +#define AFPVOL_MTOUUPPER (1 << 0) +#define AFPVOL_MTOULOWER (1 << 1) +#define AFPVOL_UTOMUPPER (1 << 2) +#define AFPVOL_UTOMLOWER (1 << 3) +#define AFPVOL_UMLOWER (AFPVOL_MTOULOWER | AFPVOL_UTOMLOWER) +#define AFPVOL_UMUPPER (AFPVOL_MTOUUPPER | AFPVOL_UTOMUPPER) +#define AFPVOL_UUPPERMLOWER (AFPVOL_MTOUUPPER | AFPVOL_UTOMLOWER) +#define AFPVOL_ULOWERMUPPER (AFPVOL_MTOULOWER | AFPVOL_UTOMUPPER) + +#define MSWINDOWS_BADCHARS ":\t\\/<>*?|\"" + +int wincheck(const struct vol *vol, const char *path); + +#define AFPVOLSIG_FLAT 0x0001 /* flat fs */ +#define AFPVOLSIG_FIX 0x0002 /* fixed ids */ +#define AFPVOLSIG_VAR 0x0003 /* variable ids */ +#define AFPVOLSIG_DEFAULT AFPVOLSIG_FIX + +/* volume attributes */ +#define VOLPBIT_ATTR_RO (1 << 0) +#define VOLPBIT_ATTR_PASSWD (1 << 1) +#define VOLPBIT_ATTR_FILEID (1 << 2) +#define VOLPBIT_ATTR_CATSEARCH (1 << 3) +#define VOLPBIT_ATTR_BLANKACCESS (1 << 4) +#define VOLPBIT_ATTR_UNIXPRIV (1 << 5) +#define VOLPBIT_ATTR_UTF8 (1 << 6) +#define VOLPBIT_ATTR_NONETUID (1 << 7) +#define VOLPBIT_ATTR_EXT_ATTRS (1 << 10) +#define VOLPBIT_ATTR_ACLS (1 << 11) +#define VOLPBIT_ATTR_TM (1 << 13) + +#define VOLPBIT_ATTR 0 +#define VOLPBIT_SIG 1 +#define VOLPBIT_CDATE 2 +#define VOLPBIT_MDATE 3 +#define VOLPBIT_BDATE 4 +#define VOLPBIT_VID 5 +#define VOLPBIT_BFREE 6 +#define VOLPBIT_BTOTAL 7 +#define VOLPBIT_NAME 8 +/* handle > 4GB volumes */ +#define VOLPBIT_XBFREE 9 +#define VOLPBIT_XBTOTAL 10 +#define VOLPBIT_BSIZE 11 /* block size */ + + +#define vol_noadouble(vol) (((vol)->v_flags & AFPVOL_NOADOUBLE) ? \ + ADFLAGS_NOADOUBLE : 0) +#ifdef AFP3x +#define utf8_encoding() (afp_version >= 30) +#else +#define utf8_encoding() (0) +#endif + +#define vol_nodev(vol) (((vol)->v_flags & AFPVOL_NODEV) ? 1 : 0) +#define vol_unix_priv(vol) (afp_version >= 30 && ((vol)->v_flags & AFPVOL_UNIX_PRIV)) +#define vol_inv_dots(vol) (((vol)->v_flags & AFPVOL_INV_DOTS) ? 1 : 0) + + +#endif diff --git a/libatalk/Makefile.am b/libatalk/Makefile.am index de222108..5fcf4483 100644 --- a/libatalk/Makefile.am +++ b/libatalk/Makefile.am @@ -1,7 +1,7 @@ # Makefile.am for libatalk/ -SUBDIRS = adouble asp atp compat cnid dsi nbp netddp util tdb unicode acl +SUBDIRS = acl adouble asp atp compat cnid dsi nbp netddp tdb util unicode vfs lib_LTLIBRARIES = libatalk.la @@ -16,8 +16,9 @@ libatalk_la_LIBADD = \ nbp/libnbp.la \ netddp/libnetddp.la \ util/libutil.la \ - tdb/libtdb.la \ - unicode/libunicode.la @LIBATALK_ACLS@ + tdb/libtdb.la \ + unicode/libunicode.la \ + vfs/libvfs.la @LIBATALK_ACLS@ libatalk_la_DEPENDENCIES = \ adouble/libadouble.la \ @@ -28,8 +29,9 @@ libatalk_la_DEPENDENCIES = \ nbp/libnbp.la \ netddp/libnetddp.la \ util/libutil.la \ - tdb/libtdb.la \ - unicode/libunicode.la @LIBATALK_ACLS@ + tdb/libtdb.la \ + unicode/libunicode.la \ + vfs/libvfs.la @LIBATALK_ACLS@ libatalk_la_LDFLAGS = -static diff --git a/libatalk/adouble/Makefile.am b/libatalk/adouble/Makefile.am index 299ab344..8a242c2a 100644 --- a/libatalk/adouble/Makefile.am +++ b/libatalk/adouble/Makefile.am @@ -2,6 +2,7 @@ noinst_LTLIBRARIES = libadouble.la -libadouble_la_SOURCES = ad_open.c ad_flush.c ad_read.c ad_write.c ad_size.c ad_mmap.c ad_lock.c ad_date.c ad_attr.c ad_sendfile.c +libadouble_la_SOURCES = ad_open.c ad_flush.c ad_read.c ad_write.c ad_size.c \ + ad_mmap.c ad_lock.c ad_date.c ad_attr.c ad_sendfile.c noinst_HEADERS = ad_private.h diff --git a/libatalk/adouble/ad_open.c b/libatalk/adouble/ad_open.c index 4f62e952..6d9bf8f6 100644 --- a/libatalk/adouble/ad_open.c +++ b/libatalk/adouble/ad_open.c @@ -1,5 +1,5 @@ /* - * $Id: ad_open.c,v 1.47 2009-09-14 00:02:21 didg Exp $ + * $Id: ad_open.c,v 1.48 2009-10-02 09:32:41 franklahm Exp $ * * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu) * Copyright (c) 1990,1991 Regents of The University of Michigan. @@ -219,6 +219,8 @@ static int ad_update(struct adouble *ad, const char *path) if (!path || ad->ad_flags != AD_VERSION2) return 0; + LOG(log_maxdebug, logtype_default, "ad_update: checking whether '%s' needs an upgrade.", path); + if (!(ad->ad_md->adf_flags & O_RDWR)) { /* we were unable to open the file read write the last time */ return 0; diff --git a/libatalk/util/Makefile.am b/libatalk/util/Makefile.am index f64d133d..2ae09252 100644 --- a/libatalk/util/Makefile.am +++ b/libatalk/util/Makefile.am @@ -19,6 +19,7 @@ libutil_la_SOURCES = \ strdicasecmp.c \ strlcpy.c \ fault.c \ - volinfo.c + volinfo.c \ + locking.c # util_unicode.c diff --git a/libatalk/util/locking.c b/libatalk/util/locking.c new file mode 100644 index 00000000..f42edac8 --- /dev/null +++ b/libatalk/util/locking.c @@ -0,0 +1,49 @@ +/* + $Id: locking.c,v 1.1 2009-10-02 09:32:41 franklahm Exp $ + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +#include +#include + +/* + * Function: lock_reg + * + * Purpose: lock a file with fctnl + * + * Arguments: + * + * fd (r) File descriptor + * cmd (r) cmd to fcntl, only F_SETLK is usable here + * type (r) F_RDLCK, F_WRLCK, F_UNLCK + * offset (r) byte offset relative to l_whence + * whence (r) SEEK_SET, SEEK_CUR, SEEK_END + * len (r) no. of bytes (0 means to EOF) + * + * Returns: fcntl return value + * + * Effects: + * + * Function called by macros {read|write|un]_lock to ease locking. + */ +int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len) +{ + struct flock lock; + + lock.l_type = type; + lock.l_start = offset; + lock.l_whence = whence; + lock.l_len = len; + + return (fcntl(fd, cmd, &lock)); +} diff --git a/libatalk/util/volinfo.c b/libatalk/util/volinfo.c index dbb44e10..167c93c9 100644 --- a/libatalk/util/volinfo.c +++ b/libatalk/util/volinfo.c @@ -96,7 +96,6 @@ static const _vol_opt_name vol_opt_names[] = { {AFPVOL_EILSEQ, "ILLEGALSEQ"}, /* encode illegal sequence */ {AFPVOL_CACHE, "CACHEID"}, /* Use adouble v2 CNID caching, default don't use it */ {AFPVOL_INV_DOTS, "INVISIBLEDOTS"}, - {AFPVOL_EXT_ATTRS, "EXT_ATTRS"}, /* Vol supports Extened Attributes */ {AFPVOL_ACLS, "ACLS"}, /* Vol supports ACLs */ {AFPVOL_TM, "TM"}, /* Set "kSupportsTMLockSteal" is volume attributes */ {0, NULL} diff --git a/libatalk/vfs/.cvsignore b/libatalk/vfs/.cvsignore new file mode 100644 index 00000000..70177ec7 --- /dev/null +++ b/libatalk/vfs/.cvsignore @@ -0,0 +1,7 @@ +Makefile +Makefile.in +*.lo +*.la +*.loT +.deps +.libs diff --git a/libatalk/vfs/Makefile.am b/libatalk/vfs/Makefile.am new file mode 100644 index 00000000..f208caf1 --- /dev/null +++ b/libatalk/vfs/Makefile.am @@ -0,0 +1,10 @@ +# Makefile.am for libatalk/adouble/ + +noinst_LTLIBRARIES = libvfs.la + +libvfs_la_SOURCES = vfs.c unix.c ea.c + +if USE_NFSv4_ACLS +libvfs_la_SOURCES += acl.c +endif + diff --git a/libatalk/vfs/acl.c b/libatalk/vfs/acl.c new file mode 100644 index 00000000..d851f706 --- /dev/null +++ b/libatalk/vfs/acl.c @@ -0,0 +1,110 @@ +/* + $Id: acl.c,v 1.1 2009-10-02 09:32:41 franklahm Exp $ + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include +#include + +#include +#include + +/* Get ACL. Allocates storage as needed. Caller must free. + * Returns no of ACEs or -1 on error. */ +int get_nfsv4_acl(const char *name, ace_t **retAces) +{ + int ace_count = -1; + ace_t *aces; + + *retAces = NULL; + ace_count = acl(name, ACE_GETACLCNT, 0, NULL); + if (ace_count <= 0) { + LOG(log_error, logtype_afpd, "get_nfsv4_acl: acl(ACE_GETACLCNT) error"); + return -1; + } + + aces = malloc(ace_count * sizeof(ace_t)); + if (aces == NULL) { + LOG(log_error, logtype_afpd, "get_nfsv4_acl: malloc error"); + return -1; + } + + if ( (acl(name, ACE_GETACL, ace_count, aces)) == -1 ) { + LOG(log_error, logtype_afpd, "get_nfsv4_acl: acl(ACE_GETACL) error"); + free(aces); + return -1; + } + + LOG(log_debug9, logtype_afpd, "get_nfsv4_acl: file: %s -> No. of ACEs: %d", name, ace_count); + *retAces = aces; + + return ace_count; +} + +/* Removes all non-trivial ACLs from object. Returns full AFPERR code. */ +int remove_acl(const char *name) +{ + int ret,i, ace_count, trivial_aces, new_aces_count; + ace_t *old_aces = NULL; + ace_t *new_aces = NULL; + + LOG(log_debug9, logtype_afpd, "remove_acl: BEGIN"); + + /* Get existing ACL and count trivial ACEs */ + if ((ace_count = get_nfsv4_acl(name, &old_aces)) == -1) + return AFPERR_MISC; + trivial_aces = 0; + for ( i=0; i < ace_count; i++) { + if (old_aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE)) + trivial_aces++; + } + + /* malloc buffer for new ACL */ + if ((new_aces = malloc(trivial_aces * sizeof(ace_t))) == NULL) { + LOG(log_error, logtype_afpd, "remove_acl: malloc %s", strerror(errno)); + ret = AFPERR_MISC; + goto exit; + } + + /* Now copy the trivial ACEs */ + new_aces_count = 0; + for (i=0; i < ace_count; i++) { + if (old_aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE)) { + memcpy(&new_aces[new_aces_count], &old_aces[i], sizeof(ace_t)); + new_aces_count++; + } + } + + if ( (acl(name, ACE_SETACL, trivial_aces, new_aces)) == 0) + ret = AFP_OK; + else { + LOG(log_error, logtype_afpd, "set_acl: error setting acl: %s", strerror(errno)); + if (errno == (EACCES | EPERM)) + ret = AFPERR_ACCESS; + else if (errno == ENOENT) + ret = AFPERR_NOITEM; + else + ret = AFPERR_MISC; + } + +exit: + free(old_aces); + free(new_aces); + + LOG(log_debug9, logtype_afpd, "remove_acl: END"); + return ret; +} diff --git a/libatalk/vfs/ea.c b/libatalk/vfs/ea.c new file mode 100644 index 00000000..e776ec67 --- /dev/null +++ b/libatalk/vfs/ea.c @@ -0,0 +1,1509 @@ +/* + $Id: ea.c,v 1.1 2009-10-02 09:32:41 franklahm Exp $ + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + 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 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* + * Store Extended Attributes inside .AppleDouble folders as follows: + * + * filename "fileWithEAs" with EAs "testEA1" and "testEA2" + * + * - create header with with the format struct adouble_ea_ondisk, the file is written to + * ".AppleDouble/fileWithEAs::EA" + * - store EAs in files "fileWithEAs::EA::testEA1" and "fileWithEAs::EA::testEA2" + */ + +/* + * Function: unpack_header + * + * Purpose: unpack and verify header file data buffer at ea->ea_data into struct ea + * + * Arguments: + * + * ea (rw) handle to struct ea + * + * Returns: 0 on success, -1 on error + * + * Effects: + * + * Verifies magic and version. + */ +static int unpack_header(struct ea * restrict ea) +{ + int ret = 0, count = 0; + uint32_t uint32; + char *buf; + + /* Check magic and version */ + buf = ea->ea_data; + if (*(uint32_t *)buf != htonl(EA_MAGIC)) { + LOG(log_error, logtype_afpd, "unpack_header: wrong magic 0x%08x", *(uint32_t *)buf); + ret = -1; + goto exit; + } + buf += 4; + if (*(uint16_t *)buf != htons(EA_VERSION)) { + LOG(log_error, logtype_afpd, "unpack_header: wrong version 0x%04x", *(uint16_t *)buf); + ret = -1; + goto exit; + } + buf += 2; + + /* Get EA count */ + ea->ea_count = ntohs(*(uint16_t *)buf); + LOG(log_debug, logtype_afpd, "unpack_header: number of EAs: %u", ea->ea_count); + buf += 2; + + if (ea->ea_count == 0) + return 0; + + /* Allocate storage for the ea_entries array */ + ea->ea_entries = malloc(sizeof(struct ea_entry) * ea->ea_count); + if ( ! ea->ea_entries) { + LOG(log_error, logtype_afpd, "unpack_header: OOM"); + ret = -1; + goto exit; + } + + buf = ea->ea_data + EA_HEADER_SIZE; + while (count < ea->ea_count) { + memcpy(&uint32, buf, 4); /* EA size */ + buf += 4; + (*(ea->ea_entries))[count].ea_size = ntohl(uint32); + (*(ea->ea_entries))[count].ea_name = strdup(buf); + if (! (*(ea->ea_entries))[count].ea_name) { + LOG(log_error, logtype_afpd, "unpack_header: OOM"); + ret = -1; + goto exit; + } + (*(ea->ea_entries))[count].ea_namelen = strlen((*(ea->ea_entries))[count].ea_name); + buf += (*(ea->ea_entries))[count].ea_namelen + 1; + + LOG(log_maxdebug, logtype_afpd, "unpack_header: entry no:%u,\"%s\", size: %u, namelen: %u", count, + (*(ea->ea_entries))[count].ea_name, + (*(ea->ea_entries))[count].ea_size, + (*(ea->ea_entries))[count].ea_namelen); + + count++; + } + +exit: + return ret; +} + +/* + * Function: pack_header + * + * Purpose: pack everything from struct ea into buffer at ea->ea_data + * + * Arguments: + * + * ea (rw) handle to struct ea + * + * Returns: 0 on success, -1 on error + * + * Effects: + * + * adjust ea->ea_count in case an ea entry deletetion is detected + */ +static int pack_header(struct ea * restrict ea) +{ + int count = 0, eacount = 0; + uint16_t uint16; + uint32_t uint32; + size_t bufsize = EA_HEADER_SIZE; + + char *buf = ea->ea_data + EA_HEADER_SIZE; + + LOG(log_debug, logtype_afpd, "pack_header('%s'): ea_count: %u, ea_size: %u", + ea->filename, ea->ea_count, ea->ea_size); + + if (ea->ea_count == 0) + /* nothing to do, magic, version and count are still valid in buffer */ + return 0; + + while(count < ea->ea_count) { /* the names */ + /* Check if its a deleted entry */ + if ( ! ((*ea->ea_entries)[count].ea_name)) { + count++; + continue; + } + + bufsize += (*(ea->ea_entries))[count].ea_namelen + 1; + count++; + eacount++; + } + + bufsize += (eacount * 4); /* header + ea_size for each EA */ + if (bufsize > ea->ea_size) { + /* we must realloc */ + if ( ! (buf = realloc(ea->ea_data, bufsize)) ) { + LOG(log_error, logtype_afpd, "pack_header: OOM"); + return -1; + } + ea->ea_data = buf; + } + ea->ea_size = bufsize; + + /* copy count */ + uint16 = htons(eacount); + memcpy(ea->ea_data + EA_COUNT_OFF, &uint16, 2); + + count = 0; + buf = ea->ea_data + EA_HEADER_SIZE; + while (count < ea->ea_count) { + /* Check if its a deleted entry */ + if ( ! ((*ea->ea_entries)[count].ea_name)) { + count++; + continue; + } + + /* First: EA size */ + uint32 = htonl((*(ea->ea_entries))[count].ea_size); + memcpy(buf, &uint32, 4); + buf += 4; + + /* Second: EA name as C-string */ + strcpy(buf, (*(ea->ea_entries))[count].ea_name); + buf += (*(ea->ea_entries))[count].ea_namelen + 1; + + LOG(log_maxdebug, logtype_afpd, "pack_header: entry no:%u,\"%s\", size: %u, namelen: %u", count, + (*(ea->ea_entries))[count].ea_name, + (*(ea->ea_entries))[count].ea_size, + (*(ea->ea_entries))[count].ea_namelen); + + count++; + } + + ea->ea_count = eacount; + + LOG(log_debug, logtype_afpd, "pack_header('%s'): ea_count: %u, ea_size: %u", + ea->filename, ea->ea_count, ea->ea_size); + + return 0; +} + +/* + * Function: ea_path + * + * Purpose: return name of ea header filename + * + * Arguments: + * + * ea (r) ea handle + * eaname (r) name of EA or NULL + * + * Returns: pointer to name in static buffer + * + * Effects: + * + * Calls ad_open, copies buffer, appends "::EA" and if supplied append eanme + * Files: "file" -> "file/.AppleDouble/file::EA" + * Dirs: "dir" -> "dir/.AppleDouble/.Parent::EA" + * "file" with EA "myEA" -> "file/.AppleDouble/file::EA:myEA" + */ +static char * ea_path(const struct ea * restrict ea, + const char * restrict eaname) +{ + char *adname; + 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); + /* copy it so we can work with it */ + strlcpy(pathbuf, adname, MAXPATHLEN + 1); + /* append "::EA" */ + strlcat(pathbuf, "::EA", MAXPATHLEN + 1); + + if (eaname) { + strlcat(pathbuf, "::", MAXPATHLEN + 1); + strlcat(pathbuf, eaname, MAXPATHLEN + 1); + } + + return pathbuf; +} + +/* + * Function: ea_addentry + * + * Purpose: add one EA into ea->ea_entries[] + * + * Arguments: + * + * ea (rw) pointer to struct ea + * uname (r) name of file + * attruname (r) name of EA + * attrsize (r) size of ea + * + * Returns: new number of EA entries, -1 on error + * + * Effects: + * + * Grow array ea->ea_entries[]. If ea->ea_entries is still NULL, start allocating. + * Otherwise realloc and put entry at the end. Increments ea->ea_count. + */ +static int ea_addentry(struct ea * restrict ea, + const char * restrict uname, + const char * restrict attruname, + size_t attrsize) +{ + void *tmprealloc; + + if (ea->ea_count == 0) { + ea->ea_entries = malloc(sizeof(struct ea_entry)); + if ( ! ea->ea_entries) { + LOG(log_error, logtype_afpd, "ea_addentry: OOM"); + return -1; + } + } else { + tmprealloc = realloc(ea->ea_entries, sizeof(struct ea_entry) * (ea->ea_count + 1)); + if ( ! tmprealloc) { + LOG(log_error, logtype_afpd, "ea_addentry: OOM"); + return -1; + } + ea->ea_entries = tmprealloc; + } + + /* We've grown the array, now store the entry */ + (*(ea->ea_entries))[ea->ea_count].ea_size = attrsize; + (*(ea->ea_entries))[ea->ea_count].ea_name = strdup(attruname); + if ( ! (*(ea->ea_entries))[ea->ea_count].ea_name) { + LOG(log_error, logtype_afpd, "ea_addentry: OOM"); + goto error; + } + (*(ea->ea_entries))[ea->ea_count].ea_namelen = strlen(attruname); + + ea->ea_count++; + return ea->ea_count; + +error: + if (ea->ea_count == 0 && ea->ea_entries) { + /* We just allocated storage but had an error somewhere -> free storage*/ + free(ea->ea_entries); + ea->ea_entries = NULL; + } + ea->ea_count = 0; + return -1; +} + +/* + * Function: ea_delentry + * + * Purpose: delete one EA from ea->ea_entries[] + * + * Arguments: + * + * ea (rw) pointer to struct ea + * uname (r) name of EA + * attruname (r) size of ea + * + * Returns: new number of EA entries, -1 on error + * + * Effects: + * + * Remove entry from ea->ea_entries[]. Decrement ea->ea_count. + * Marks it as unused just by freeing name and setting it to NULL. + * ea_close and pack_buffer must honor this. + */ +static int ea_delentry(struct ea * restrict ea, + const char * restrict uname, + const char * restrict attruname) +{ + int ret = 0, count = 0; + + if (ea->ea_count == 0) { + return -1; + } + + while (count < ea->ea_count) { + /* search matching EA */ + if (strcmp(attruname, (*ea->ea_entries)[count].ea_name) == 0) { + free((*ea->ea_entries)[count].ea_name); + (*ea->ea_entries)[count].ea_name = NULL; + + LOG(log_debug, logtype_afpd, "ea_delentry('%s'): deleted no %u/%u", + attruname, count + 1, ea->ea_count); + + break; + } + count++; + } + + return ret; +} + +/* + * Function: create_ea_header + * + * Purpose: create EA header file, only called from ea_open + * + * Arguments: + * + * uname (r) filename for which we have to create a header + * ea (rw) ea handle with already allocated storage pointed to + * by ea->ea_data + * + * Returns: fd of open header file on success, -1 on error, errno semantics: + * EEXIST: open with O_CREAT | O_EXCL failed + * + * Effects: + * + * Creates EA header file and initialize ea->ea_data buffer. + * Possibe race condition with other afpd processes: + * we were called because header file didn't exist in eg. ea_open. We then + * try to create a file with O_CREAT | O_EXCL, but the whole process in not atomic. + * What do we do then? Someone else is in the process of creating the header too, but + * it might not have finished it. That means we cant just open, read and use it! + * We therefor currently just break with an error. + * On return the header file is still r/w locked. + */ +static int create_ea_header(const char * restrict uname, + struct ea * restrict ea) +{ + int fd = -1, err = 0; + char *ptr; + + if ((fd = open(uname, O_RDWR | O_CREAT | O_EXCL, 0666 & ~ea->vol->v_umask)) == -1) { + LOG(log_error, logtype_afpd, "ea_create: open race condition with ea header for file: %s", uname); + return -1; + } + + /* lock it */ + if ((write_lock(fd, 0, SEEK_SET, 0)) != 0) { + LOG(log_error, logtype_afpd, "ea_create: lock race condition with ea header for file: %s", uname); + err = -1; + goto exit; + } + + /* Now init it */ + ptr = ea->ea_data; + *(uint32_t *)ptr = htonl(EA_MAGIC); + ptr += EA_MAGIC_LEN; + *(uint16_t *)ptr = htons(EA_VERSION); + ptr += EA_VERSION_LEN; + *(uint16_t *)ptr = 0; /* count */ + + ea->ea_size = EA_HEADER_SIZE; + +exit: + if (err != 0) { + close(fd); + fd = -1; + } + return fd; +} + +/* + * Function: write_ea + * + * Purpose: write an EA to disk + * + * Arguments: + * + * ea (r) struct ea handle + * attruname (r) EA name + * ibuf (r) buffer with EA content + * attrsize (r) size of EA + * + * Returns: 0 on success, -1 on error + * + * Effects: + * + * Creates/overwrites EA file. + * + */ +static int write_ea(const struct ea * restrict ea, + const char * restrict attruname, + const char * restrict ibuf, + size_t attrsize) +{ + int fd = -1, ret = AFP_OK; + struct stat st; + char *eaname; + + eaname = ea_path(ea, attruname); + LOG(log_maxdebug, logtype_afpd, "write_ea: ea_apth: %s", eaname); + + /* Check if it exists, remove if yes*/ + if ((stat(eaname, &st)) == 0) { + if ((unlink(eaname)) != 0) { + if (errno == EACCES) + return AFPERR_ACCESS; + else + return AFPERR_MISC; + } + } + + if ((fd = open(eaname, O_RDWR | O_CREAT | O_EXCL, 0666 & ~ea->vol->v_umask)) == -1) { + LOG(log_error, logtype_afpd, "write_ea: open race condition: %s", eaname); + return -1; + } + + /* lock it */ + if ((write_lock(fd, 0, SEEK_SET, 0)) != 0) { + LOG(log_error, logtype_afpd, "write_ea: open race condition: %s", eaname); + ret = -1; + goto exit; + } + + if ((write(fd, ibuf, attrsize)) != attrsize) { + LOG(log_error, logtype_afpd, "write_ea: short write: %s", eaname); + ret = -1; + goto exit; + } + +exit: + if (fd != -1) + close(fd); /* and unlock */ + return ret; +} + +/* + * Function: delete_ea_file + * + * Purpose: delete EA file from disk + * + * Arguments: + * + * ea (r) struct ea handle + * attruname (r) EA name + * + * Returns: 0 on success, -1 on error + */ +static int delete_ea_file(const struct ea * restrict ea, + const char *eaname) +{ + int ret = 0; + char *eafile; + struct stat st; + + eafile = ea_path(ea, eaname); + + /* Check if it exists, remove if yes*/ + if ((stat(eafile, &st)) == 0) { + if ((unlink(eafile)) != 0) { + LOG(log_error, logtype_afpd, "delete_ea_file('%s'): unlink: %s", + eafile, strerror(errno)); + ret = -1; + } else + LOG(log_debug, logtype_afpd, "delete_ea_file('%s'): success", eafile); + } + + return ret; +} + +/* + * Function: ea_open + * + * Purpose: open EA header file, create if it doesnt exits and called with O_CREATE + * + * Arguments: + * + * vol (r) current volume + * uname (r) filename for which we have to open a header + * flags (r) EA_CREATE: create if it doesn't exist (without it won't be created) + * EA_RDONLY: open read only + * EA_RDWR: open read/write + * Eiterh EA_RDONLY or EA_RDWR MUST be requested + * ea (w) pointer to a struct ea that we fill + * + * Returns: 0 on success, -1 on error + * + * Effects: + * + * opens header file and stores fd in ea->ea_fd. Size of file is put into ea->ea_size. + * number of EAs is stored in ea->ea_count. flags are remembered in ea->ea_flags. + * file is either read or write locked depending on the open flags. + * When you're done with struct ea you must call ea_close on it. + */ +static int ea_open(const struct vol * restrict vol, + const char * restrict uname, + eaflags_t eaflags, + struct ea * restrict ea) +{ + int ret = 0; + char *eaname; + struct stat st; + + /* Enforce usage rules! */ + if ( ! (eaflags & (EA_RDONLY | EA_RDWR))) { + LOG(log_error, logtype_afpd, "ea_open: called without EA_RDONLY | EA_RDWR", uname); + return -1; + } + + if ((stat(uname, &st)) != 0) { + LOG(log_debug, logtype_afpd, "ea_open: cant stat: %s", uname); + return -1; + } + + /* set it all to 0 */ + memset(ea, 0, sizeof(struct ea)); + + ea->vol = vol; /* ea_close needs it */ + + ea->ea_flags = eaflags; + if (S_ISDIR(st.st_mode)) + ea->ea_flags |= EA_DIR; + + if ( ! (ea->filename = strdup(uname))) { + LOG(log_error, logtype_afpd, "ea_open: OOM"); + return -1; + } + + eaname = ea_path(ea, NULL); + LOG(log_maxdebug, logtype_afpd, "ea_open: ea_path: %s", eaname); + + /* Check if it exists, if not create it if EA_CREATE is in eaflags */ + if ((stat(eaname, &st)) != 0) { + if (errno == ENOENT) { + + /* It doesnt exist */ + + if ( ! (eaflags & EA_CREATE)) { + /* creation was not requested, so return with error */ + ret = -1; + goto exit; + } + + /* Now create a header file */ + + /* malloc buffer for minimal on disk data */ + ea->ea_data = malloc(EA_HEADER_SIZE); + if (! ea->ea_data) { + LOG(log_error, logtype_afpd, "ea_open: OOM"); + ret = -1; + goto exit; + } + + /* create it */ + ea->ea_fd = create_ea_header(eaname, ea); + if (ea->ea_fd == -1) { + ret = -1; + goto exit; + } + + return 0; + + } else {/* errno != ENOENT */ + ret = -1; + goto exit; + } + } + + /* header file exists, so read and parse it */ + + /* malloc buffer where we read disk file into */ + ea->ea_size = st.st_size; + ea->ea_data = malloc(st.st_size); + if (! ea->ea_data) { + LOG(log_error, logtype_afpd, "ea_open: OOM"); + ret = -1; + goto exit; + } + + /* 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); + ret = -1; + goto exit; + } + + /* lock it */ + if (ea->ea_flags & EA_RDONLY) { + /* read lock */ + if ((read_lock(ea->ea_fd, 0, SEEK_SET, 0)) != 0) { + LOG(log_error, logtype_afpd, "ea_open: lock error on header: %s", eaname); + ret = -1; + goto exit; + } + } else { /* EA_RDWR */ + /* write lock */ + if ((write_lock(ea->ea_fd, 0, SEEK_SET, 0)) != 0) { + LOG(log_error, logtype_afpd, "ea_open: lock error on header: %s", eaname); + ret = -1; + goto exit; + } + } + + /* read it */ + if ((read(ea->ea_fd, ea->ea_data, ea->ea_size)) != ea->ea_size) { + LOG(log_error, logtype_afpd, "ea_open: short read on header: %s", eaname); + ret = -1; + goto exit; + } + + if ((unpack_header(ea)) != 0) { + LOG(log_error, logtype_afpd, "ea_open: error unpacking header for: %s", eaname); + ret = -1; + goto exit; + } + +exit: + if (ret != 0) { + if (ea->ea_data) { + free(ea->ea_data); + ea->ea_data = NULL; + } + if (ea->ea_fd) { + close(ea->ea_fd); + ea->ea_fd = -1; + } + } + return ret; +} + +/* + * Function: ea_close + * + * Purpose: flushes and closes an ea handle + * + * Arguments: + * + * ea (rw) pointer to ea handle + * + * Returns: 0 on success, -1 on error + * + * Effects: + * + * Flushes and then closes and frees all resouces held by ea handle. + * Pack data in ea into ea_data, then write ea_data to disk + */ +static int ea_close(struct ea * restrict ea) +{ + int ret = 0, count = 0; + char *eaname; + struct stat st; + + LOG(log_debug, logtype_afpd, "ea_close('%s')", ea->filename); + + /* pack header and write it to disk if it was opened EA_RDWR*/ + if (ea->ea_flags & EA_RDWR) { + if ((pack_header(ea)) != 0) { + LOG(log_error, logtype_afpd, "ea_close: pack header"); + ret = -1; + } else { + if (ea->ea_count == 0) { + /* Check if EA header exists and remove it */ + eaname = ea_path(ea, NULL); + if ((stat(eaname, &st)) == 0) { + if ((unlink(eaname)) != 0) { + LOG(log_error, logtype_afpd, "ea_close('%s'): unlink: %s", + eaname, strerror(errno)); + ret = -1; + } + else + LOG(log_debug, logtype_afpd, "ea_close(unlink '%s'): success", eaname); + } else { + /* stat error */ + if (errno != ENOENT) { + LOG(log_error, logtype_afpd, "ea_close('%s'): stat: %s", + eaname, strerror(errno)); + ret = -1; + } + } + } else { /* ea->ea_count > 0 */ + if ((lseek(ea->ea_fd, 0, SEEK_SET)) == -1) { + LOG(log_error, logtype_afpd, "ea_close: lseek: %s", strerror(errno)); + ret = -1; + goto exit; + } + + if ((ftruncate(ea->ea_fd, 0)) == -1) { + LOG(log_error, logtype_afpd, "ea_close: ftruncate: %s", strerror(errno)); + ret = -1; + goto exit; + } + + if (write(ea->ea_fd, ea->ea_data, ea->ea_size) != ea->ea_size) { + LOG(log_error, logtype_afpd, "ea_close: write: %s", strerror(errno)); + ret = -1; + } + } + } + } + +exit: + /* free names */ + while(count < ea->ea_count) { + if ( (*ea->ea_entries)[count].ea_name ) { + free((*ea->ea_entries)[count].ea_name); + (*ea->ea_entries)[count].ea_name = NULL; + } + count++; + } + ea->ea_count = 0; + + if (ea->filename) { + free(ea->filename); + ea->filename = NULL; + } + + if (ea->ea_entries) { + free(ea->ea_entries); + ea->ea_entries = NULL; + } + + if (ea->ea_data) { + free(ea->ea_data); + ea->ea_data = NULL; + } + if (ea->ea_fd != -1) { + close(ea->ea_fd); /* also releases the fcntl lock */ + ea->ea_fd = -1; + } + + return 0; +} + + + +/************************************************************************************ + * VFS funcs called from afp_ea* funcs + ************************************************************************************/ + +/* + * Function: get_easize + * + * Purpose: get size of an 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. + */ +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 ret = AFPERR_MISC, count = 0; + uint32_t uint32; + struct ea ea; + + LOG(log_debug, logtype_afpd, "get_easize: file: %s", uname); + + if ((ea_open(vol, uname, EA_RDONLY, &ea)) != 0) { + LOG(log_error, logtype_afpd, "get_easize: error calling ea_open for file: %s", uname); + return AFPERR_MISC; + } + + while (count < ea.ea_count) { + if (strcmp(attruname, (*ea.ea_entries)[count].ea_name) == 0) { + uint32 = htonl((*ea.ea_entries)[count].ea_size); + memcpy(rbuf, &uint32, 4); + *rbuflen += 4; + ret = AFP_OK; + + LOG(log_debug, logtype_afpd, "get_easize(\"%s\"): size: %u", + attruname, (*ea.ea_entries)[count].ea_size); + break; + } + count++; + } + + if ((ea_close(&ea)) != 0) { + LOG(log_error, logtype_afpd, "get_easize: error closing ea handle for file: %s", uname); + return AFPERR_MISC; + } + + return ret; +} + +/* + * Function: get_eacontent + * + * Purpose: copy 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. + */ +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 ret = AFPERR_MISC, count = 0, fd = -1; + uint32_t uint32; + size_t toread; + struct ea ea; + + LOG(log_debug, logtype_afpd, "get_eacontent('%s/%s')", uname, attruname); + + if ((ea_open(vol, uname, EA_RDONLY, &ea)) != 0) { + LOG(log_error, logtype_afpd, "get_eacontent('%s'): ea_open error", uname); + return AFPERR_MISC; + } + + 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) { + ret = AFPERR_MISC; + break; + } + + /* Check how much the client wants, give him what we think is right */ + maxreply -= MAX_REPLY_EXTRA_BYTES; + if (maxreply > MAX_EA_SIZE) + maxreply = MAX_EA_SIZE; + toread = (maxreply < (*ea.ea_entries)[count].ea_size) ? maxreply : (*ea.ea_entries)[count].ea_size; + LOG(log_debug, logtype_afpd, "get_eacontent('%s'): sending %u bytes", attruname, toread); + + /* Put length of EA data in reply buffer */ + uint32 = htonl(toread); + memcpy(rbuf, &uint32, 4); + rbuf += 4; + *rbuflen += 4; + + if ((read(fd, rbuf, toread)) != toread) { + LOG(log_error, logtype_afpd, "get_eacontent('%s/%s'): short read", uname, attruname); + ret = AFPERR_MISC; + break; + } + *rbuflen += toread; + close(fd); + + ret = AFP_OK; + break; + } + count++; + } + + if ((ea_close(&ea)) != 0) { + LOG(log_error, logtype_afpd, "get_eacontent('%s'): error closing ea handle", uname); + return AFPERR_MISC; + } + + return ret; + +} + +/* + * Function: list_eas + * + * Purpose: copy names of EAs 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 *buflen accordingly. + */ +int list_eas(const struct vol * restrict vol, + char * restrict attrnamebuf, + int * restrict buflen, + const char * restrict uname, + int oflag) +{ + int count = 0, attrbuflen = *buflen, ret = AFP_OK, len; + char *buf = attrnamebuf; + struct ea ea; + + LOG(log_debug, logtype_afpd, "list_eas: file: %s", uname); + + if ((ea_open(vol, uname, EA_RDONLY, &ea)) != 0) { + if (errno != ENOENT) { + LOG(log_error, logtype_afpd, "list_eas: error calling ea_open for file: %s", uname); + return AFPERR_MISC; + } + else + return AFP_OK; + } + + while (count < ea.ea_count) { + /* Convert name to CH_UTF8_MAC and directly store in in the reply buffer */ + if ( ( len = convert_string(vol->v_volcharset, + CH_UTF8_MAC, + (*ea.ea_entries)[count].ea_name, + (*ea.ea_entries)[count].ea_namelen, + buf + attrbuflen, + 255)) + <= 0 ) { + ret = AFPERR_MISC; + goto exit; + } + if (len == 255) + /* convert_string didn't 0-terminate */ + attrnamebuf[attrbuflen + 255] = 0; + + LOG(log_debug7, logtype_afpd, "list_eas(%s): EA: %s", + uname, (*ea.ea_entries)[count].ea_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, "list_eas(%s): running out of buffer for EA names", uname); + ret = AFPERR_MISC; + goto exit; + } + count++; + } + +exit: + *buflen += attrbuflen; + + if ((ea_close(&ea)) != 0) { + LOG(log_error, logtype_afpd, "list_eas: error closing ea handle for file: %s", uname); + return AFPERR_MISC; + } + + return ret; +} + +/* + * Function: 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. + */ +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 ret = AFP_OK; + struct ea ea; + + LOG(log_debug, logtype_afpd, "set_ea: file: %s", uname); + + if ((ea_open(vol, uname, EA_CREATE | EA_RDWR, &ea)) != 0) { + LOG(log_error, logtype_afpd, "set_ea('%s'): ea_open error", uname); + return AFPERR_MISC; + } + + if ((ea_addentry(&ea, uname, attruname, attrsize)) == -1) { + LOG(log_error, logtype_afpd, "set_ea('%s'): ea_addentry error", uname); + ret = AFPERR_MISC; + goto exit; + } + + if ((write_ea(&ea, attruname, ibuf, attrsize)) != 0) { + LOG(log_error, logtype_afpd, "set_ea('%s'): write_ea error", uname); + ret = AFPERR_MISC; + goto exit; + } + +exit: + if ((ea_close(&ea)) != 0) { + LOG(log_error, logtype_afpd, "set_ea('%s'): ea_close error", uname); + ret = AFPERR_MISC; + goto exit; + } + + return ret; +} + +/* + * Function: remove_ea + * + * Purpose: remove a EA from a file + * + * 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. + */ +int remove_ea(const struct vol * restrict vol, + const char * restrict uname, + const char * restrict attruname, + int oflag) +{ + int ret = AFP_OK; + struct ea ea; + + LOG(log_debug, logtype_afpd, "remove_ea('%s/%s')", uname, attruname); + + if ((ea_open(vol, uname, EA_RDWR, &ea)) != 0) { + LOG(log_error, logtype_afpd, "remove_ea('%s'): ea_open error", uname); + return AFPERR_MISC; + } + + if ((ea_delentry(&ea, uname, attruname)) == -1) { + LOG(log_error, logtype_afpd, "remove_ea('%s'): ea_delentry error", uname); + ret = AFPERR_MISC; + goto exit; + } + + if ((delete_ea_file(&ea, attruname)) != 0) { + LOG(log_error, logtype_afpd, "remove_ea('%s'): delete_ea error", uname); + ret = AFPERR_MISC; + goto exit; + } + +exit: + if ((ea_close(&ea)) != 0) { + LOG(log_error, logtype_afpd, "remove_ea('%s'): ea_close error", uname); + ret = AFPERR_MISC; + goto 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 */ diff --git a/libatalk/vfs/unix.c b/libatalk/vfs/unix.c new file mode 100644 index 00000000..056bdf02 --- /dev/null +++ b/libatalk/vfs/unix.c @@ -0,0 +1,155 @@ +/* + * $Id: unix.c,v 1.1 2009-10-02 09:32:41 franklahm Exp $ + * + * Copyright (c) 1990,1993 Regents of The University of Michigan. + * All Rights Reserved. See COPYRIGHT. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* ----------------------------- + a dropbox is a folder where w is set but not r eg: + rwx-wx-wx or rwx-wx-- + rwx----wx (is not asked by a Mac with OS >= 8.0 ?) +*/ +int stickydirmode(const char *name, const mode_t mode, const int dropbox, const mode_t v_umask) +{ + int retval = 0; + +#ifdef DROPKLUDGE + /* Turn on the sticky bit if this is a drop box, also turn off the setgid bit */ + if ((dropbox & AFPVOL_DROPBOX)) { + int uid; + + if ( ( (mode & S_IWOTH) && !(mode & S_IROTH)) || + ( (mode & S_IWGRP) && !(mode & S_IRGRP)) ) + { + uid=geteuid(); + if ( seteuid(0) < 0) { + LOG(log_error, logtype_afpd, "stickydirmode: unable to seteuid root: %s", strerror(errno)); + } + if ( (retval=chmod( name, ( (DIRBITS | mode | S_ISVTX) & ~v_umask) )) < 0) { + LOG(log_error, logtype_afpd, "stickydirmode: chmod \"%s\": %s", fullpathname(name), strerror(errno) ); + } else { + LOG(log_debug, logtype_afpd, "stickydirmode: chmod \"%s\": %s", fullpathname(name), strerror(retval) ); + } + seteuid(uid); + return retval; + } + } +#endif /* DROPKLUDGE */ + + /* + * Ignore EPERM errors: We may be dealing with a directory that is + * group writable, in which case chmod will fail. + */ + if ( (chmod( name, (DIRBITS | mode) & ~v_umask ) < 0) && errno != EPERM && + !(errno == ENOENT && (dropbox & AFPVOL_NOADOUBLE)) ) + { + LOG(log_error, logtype_afpd, "stickydirmode: chmod \"%s\": %s", fullpathname(name), strerror(errno) ); + retval = -1; + } + + return retval; +} + +/* ------------------------- */ +int dir_rx_set(mode_t mode) +{ + return (mode & (S_IXUSR | S_IRUSR)) == (S_IXUSR | S_IRUSR); +} + +/* --------------------- */ +int setfilmode(const char * name, mode_t mode, struct stat *st, mode_t v_umask) +{ +struct stat sb; +mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO; /* rwx for owner group and other, by default */ + + if (!st) { + if (stat(name, &sb) != 0) + return -1; + st = &sb; + } + + mode |= st->st_mode & ~mask; /* keep other bits from previous mode */ + if ( chmod( name, mode & ~v_umask ) < 0 && errno != EPERM ) { + return -1; + } + return 0; +} + +/* ------------------- + system rmdir with afp error code. + ENOENT is not an error. + */ +int netatalk_rmdir(const char *name) +{ + if (rmdir(name) < 0) { + switch ( errno ) { + case ENOENT : + break; + case ENOTEMPTY : + return AFPERR_DIRNEMPT; + case EPERM: + case EACCES : + return AFPERR_ACCESS; + case EROFS: + return AFPERR_VLOCK; + default : + return AFPERR_PARAM; + } + } + return AFP_OK; +} + +/* ------------------- + system unlink with afp error code. + ENOENT is not an error. + */ +int netatalk_unlink(const char *name) +{ + if (unlink(name) < 0) { + switch (errno) { + case ENOENT : + break; + case EROFS: + return AFPERR_VLOCK; + case EPERM: + case EACCES : + return AFPERR_ACCESS; + default : + return AFPERR_PARAM; + } + } + return AFP_OK; +} + +char *fullpathname(const char *name) +{ + static char wd[ MAXPATHLEN + 1]; + + if ( getcwd( wd , MAXPATHLEN) ) { + strlcat(wd, "/", MAXPATHLEN); + strlcat(wd, name, MAXPATHLEN); + } + else { + strlcpy(wd, name, MAXPATHLEN); + } + return wd; +} diff --git a/libatalk/vfs/vfs.c b/libatalk/vfs/vfs.c new file mode 100644 index 00000000..b9b4f505 --- /dev/null +++ b/libatalk/vfs/vfs.c @@ -0,0 +1,862 @@ +/* + Copyright (c) 2004 Didier Gautheron + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#include "extattrs.h" +#endif + +#ifdef HAVE_NFSv4_ACLS +extern int remove_acl(const char *name); +#endif + +struct perm { + uid_t uid; + gid_t gid; +}; + +typedef int (*rf_loop)(struct dirent *, char *, void *, int , mode_t ); + +/* ----------------------------- */ +static int +for_each_adouble(const char *from, const char *name, rf_loop fn, void *data, int flag, mode_t v_umask) +{ + char buf[ MAXPATHLEN + 1]; + char *m; + DIR *dp; + struct dirent *de; + int ret; + + + if (NULL == ( dp = opendir( name)) ) { + if (!flag) { + LOG(log_error, logtype_afpd, "%s: opendir %s: %s", from, fullpathname(name),strerror(errno) ); + return -1; + } + return 0; + } + strlcpy( buf, name, sizeof(buf)); + strlcat( buf, "/", sizeof(buf) ); + m = strchr( buf, '\0' ); + ret = 0; + while ((de = readdir(dp))) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { + continue; + } + + strlcat(buf, de->d_name, sizeof(buf)); + if (fn && (ret = fn(de, buf, data, flag, v_umask))) { + closedir(dp); + return ret; + } + *m = 0; + } + closedir(dp); + return ret; +} + +/******************************************************************************* + * classic adouble format + *******************************************************************************/ + +static int netatalk_name(const char *name) +{ + return strcasecmp(name,".AppleDB") && + strcasecmp(name,".AppleDouble") && + strcasecmp(name,".AppleDesktop"); +} + +static int validupath_adouble(const struct vol *vol, const char *name) +{ + return (vol->v_flags & AFPVOL_USEDOTS) ? + netatalk_name(name) && strcasecmp(name,".Parent"): name[0] != '.'; +} + +/* ----------------- */ +static int RF_chown_adouble(const struct vol *vol, const char *path, uid_t uid, gid_t gid) + +{ + struct stat st; + char *ad_p; + + ad_p = vol->vfs->ad_path(path, ADFLAGS_HF ); + + if ( stat( ad_p, &st ) < 0 ) + return 0; /* ignore */ + + return chown( ad_p, uid, gid ); +} + +/* ----------------- */ +int RF_renamedir_adouble(const struct vol *vol _U_, const char *oldpath _U_, const char *newpath _U_) +{ + return 0; +} + +/* ----------------- */ +static int deletecurdir_adouble_loop(struct dirent *de, char *name, void *data _U_, int flag _U_, mode_t v_umask) +{ + struct stat st; + int err; + + /* bail if the file exists in the current directory. + * note: this will not fail with dangling symlinks */ + + if (stat(de->d_name, &st) == 0) + return AFPERR_DIRNEMPT; + + if ((err = netatalk_unlink(name))) + return err; + + return 0; +} + +static int RF_deletecurdir_adouble(const struct vol *vol) +{ + int err; + + /* delete stray .AppleDouble files. this happens to get .Parent files + as well. */ + if ((err = for_each_adouble("deletecurdir", ".AppleDouble", deletecurdir_adouble_loop, NULL, 1, vol->v_umask))) + return err; + return netatalk_rmdir( ".AppleDouble" ); +} + +/* ----------------- */ +static int adouble_setfilmode(const char * name, mode_t mode, struct stat *st, mode_t v_umask) +{ + 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) +{ + return adouble_setfilmode(vol->vfs->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) +{ + char *adouble = vol->vfs->ad_path( name, ADFLAGS_DIR ); + int dropbox = vol->v_flags; + + if (dir_rx_set(mode)) { + if (stickydirmode(ad_dir(adouble), DIRBITS | mode, dropbox, vol->v_umask) < 0 ) + return -1; + } + + if (adouble_setfilmode(vol->vfs->ad_path( name, ADFLAGS_DIR ), mode, st, vol->v_umask) < 0) + return -1; + + if (!dir_rx_set(mode)) { + if (stickydirmode(ad_dir(adouble), DIRBITS | mode, dropbox, vol->v_umask) < 0 ) + return -1 ; + } + return 0; +} + +/* ----------------- */ +static int setdirmode_adouble_loop(struct dirent *de _U_, char *name, void *data, int flag, mode_t v_umask) +{ + mode_t hf_mode = *(mode_t *)data; + struct stat st; + + if ( stat( name, &st ) < 0 ) { + if (flag) + return 0; + LOG(log_error, logtype_afpd, "setdirmode: stat %s: %s", name, strerror(errno) ); + } + else if (!S_ISDIR(st.st_mode)) { + if (setfilmode(name, hf_mode , &st, v_umask) < 0) { + /* FIXME what do we do then? */ + } + } + return 0; +} + +static int RF_setdirmode_adouble(const struct vol *vol, const char * name, mode_t mode, struct stat *st _U_) +{ + int dropbox = vol->v_flags; + mode_t hf_mode = ad_hf_mode(mode); + char *adouble = vol->vfs->ad_path( name, ADFLAGS_DIR ); + char *adouble_p = ad_dir(adouble); + + if (dir_rx_set(mode)) { + if (stickydirmode(ad_dir(adouble), DIRBITS | mode, dropbox, vol->v_umask) < 0) + return -1; + } + + if (for_each_adouble("setdirmode", adouble_p, setdirmode_adouble_loop, &hf_mode, vol_noadouble(vol), vol->v_umask)) + return -1; + + if (!dir_rx_set(mode)) { + if (stickydirmode(ad_dir(adouble), DIRBITS | mode, dropbox, vol->v_umask) < 0) + return -1 ; + } + return 0; +} + +/* ----------------- */ +static int setdirowner_adouble_loop(struct dirent *de _U_, char *name, void *data, int flag _U_, mode_t v_umask _U_) +{ + struct perm *owner = data; + + if ( chown( name, owner->uid, owner->gid ) < 0 && errno != EPERM ) { + LOG(log_debug, logtype_afpd, "setdirowner: chown %d/%d %s: %s", + owner->uid, owner->gid, fullpathname(name), strerror(errno) ); + /* return ( -1 ); Sometimes this is okay */ + } + return 0; +} + +static int RF_setdirowner_adouble(const struct vol *vol, const char *name, uid_t uid, gid_t gid) + +{ + int noadouble = vol_noadouble(vol); + char *adouble_p; + struct stat st; + struct perm owner; + + owner.uid = uid; + owner.gid = gid; + + adouble_p = ad_dir(vol->vfs->ad_path( name, ADFLAGS_DIR )); + + if (for_each_adouble("setdirowner", adouble_p, setdirowner_adouble_loop, &owner, noadouble, vol->v_umask)) + return -1; + + /* + * We cheat: we know that chown doesn't do anything. + */ + if ( stat( ".AppleDouble", &st ) < 0) { + if (errno == ENOENT && noadouble) + return 0; + LOG(log_error, logtype_afpd, "setdirowner: stat %s: %s", fullpathname(".AppleDouble"), strerror(errno) ); + return -1; + } + if ( gid && gid != st.st_gid && chown( ".AppleDouble", uid, gid ) < 0 && errno != EPERM ) { + LOG(log_debug, logtype_afpd, "setdirowner: chown %d/%d %s: %s", + uid, gid,fullpathname(".AppleDouble"), strerror(errno) ); + /* return ( -1 ); Sometimes this is okay */ + } + return 0; +} + +/* ----------------- */ +static int RF_deletefile_adouble(const struct vol *vol, const char *file ) +{ + return netatalk_unlink(vol->vfs->ad_path( file, ADFLAGS_HF)); +} + +/* ----------------- */ +int RF_renamefile_adouble(const struct vol *vol, const char *src, const char *dst) +{ + 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) { + struct stat st; + + err = errno; + if (errno == ENOENT) { + struct adouble ad; + + if (stat(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. + * if we use the struct adouble passed in parameter it will not + * create .AppleDouble if the file is already opened, so we + * use a diff one, it's not a pb,ie it's not the same file, yet. + */ + 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 )) ) + err = 0; + else + err = errno; + } + else { /* it's something else, bail out */ + err = errno; + } + } + } + if (err) { + errno = err; + return -1; + } + return 0; +} + +#ifdef HAVE_NFSv4_ACLS +static int RF_acl(const struct vol *vol, const char *path, int cmd, int count, ace_t *aces) +{ + static char buf[ MAXPATHLEN + 1]; + struct stat st; + + if ((stat(path, &st)) != 0) + return -1; + if (S_ISDIR(st.st_mode)) { + if ((snprintf(buf, MAXPATHLEN, "%s/.AppleDouble",path)) < 0) + 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) + return -1; + } else + /* set ACL on ressource fork */ + if ((acl(vol->vfs->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) +{ + int ret; + static char buf[ MAXPATHLEN + 1]; + + if (dir) { + if ((snprintf(buf, MAXPATHLEN, "%s/.AppleDouble",path)) < 0) + return AFPERR_MISC; + /* remove ACL from .AppleDouble/.Parent first */ + if ((ret = remove_acl(vol->vfs->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) + return ret; + + return AFP_OK; +} +#endif + +/********************************************************************************* + * sfm adouble format + *********************************************************************************/ +static int ads_chown_loop(struct dirent *de _U_, char *name, void *data, int flag _U_, mode_t v_umask _U_) +{ + struct perm *owner = data; + + if (chown( name , owner->uid, owner->gid ) < 0) { + return -1; + } + return 0; +} + +static int RF_chown_ads(const struct vol *vol, const char *path, uid_t uid, gid_t gid) + +{ + struct stat st; + char *ad_p; + struct perm owner; + + owner.uid = uid; + owner.gid = gid; + + + ad_p = ad_dir(vol->vfs->ad_path(path, ADFLAGS_HF )); + + if ( stat( ad_p, &st ) < 0 ) { + /* ignore */ + return 0; + } + + if (chown( ad_p, uid, gid ) < 0) { + return -1; + } + return for_each_adouble("chown_ads", ad_p, ads_chown_loop, &owner, 1, vol->v_umask); +} + +/* --------------------------------- */ +static int deletecurdir_ads1_loop(struct dirent *de _U_, char *name, void *data _U_, int flag _U_, mode_t v_umask _U_) +{ + return netatalk_unlink(name); +} + +static int ads_delete_rf(char *name) +{ + int err; + + if ((err = for_each_adouble("deletecurdir", name, deletecurdir_ads1_loop, NULL, 1, 0))) + return err; + /* FIXME + * it's a problem for a nfs mounted folder, there's .nfsxxx around + * for linux the following line solve it. + * but it could fail if rm .nfsxxx create a new .nfsyyy :( + */ + if ((err = for_each_adouble("deletecurdir", name, deletecurdir_ads1_loop, NULL, 1, 0))) + return err; + return netatalk_rmdir(name); +} + +static int deletecurdir_ads_loop(struct dirent *de, char *name, void *data _U_, int flag _U_, mode_t v_umask _U_) +{ + struct stat st; + + /* bail if the file exists in the current directory. + * note: this will not fail with dangling symlinks */ + + if (stat(de->d_name, &st) == 0) { + return AFPERR_DIRNEMPT; + } + return ads_delete_rf(name); +} + +static int RF_deletecurdir_ads(const struct vol *vol _U_) +{ + 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" ); +} + +/* ------------------- */ +struct set_mode { + mode_t mode; + struct stat *st; +}; + +static int ads_setfilmode_loop(struct dirent *de _U_, char *name, void *data, int flag _U_, mode_t v_umask) +{ + struct set_mode *param = data; + + return setfilmode(name, param->mode, param->st, v_umask); +} + +static int ads_setfilmode(const char * name, mode_t mode, struct stat *st, mode_t v_umask) +{ + mode_t file_mode = ad_hf_mode(mode); + mode_t dir_mode = file_mode; + struct set_mode param; + + if ((dir_mode & (S_IRUSR | S_IWUSR ))) + dir_mode |= S_IXUSR; + if ((dir_mode & (S_IRGRP | S_IWGRP ))) + dir_mode |= S_IXGRP; + if ((dir_mode & (S_IROTH | S_IWOTH ))) + dir_mode |= S_IXOTH; + + /* change folder */ + dir_mode |= DIRBITS; + if (dir_rx_set(dir_mode)) { + if (chmod( name, dir_mode ) < 0) + return -1; + } + param.st = st; + param.mode = file_mode; + if (for_each_adouble("setfilmode_ads", name, ads_setfilmode_loop, ¶m, 0, v_umask) < 0) + return -1; + + if (!dir_rx_set(dir_mode)) { + if (chmod( name, dir_mode ) < 0) + return -1; + } + + return 0; +} + +static int RF_setfilmode_ads(const struct vol *vol, const char * name, mode_t mode, struct stat *st) +{ + return ads_setfilmode(ad_dir(vol->vfs->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) +{ + char *adouble = vol->vfs->ad_path( name, ADFLAGS_DIR ); + char ad_p[ MAXPATHLEN + 1]; + int dropbox = vol->v_flags; + + strlcpy(ad_p,ad_dir(adouble), MAXPATHLEN + 1); + + if (dir_rx_set(mode)) { + + /* .AppleDouble */ + if (stickydirmode(ad_dir(ad_p), DIRBITS | mode, dropbox, vol->v_umask) < 0) + return -1; + + /* .AppleDouble/.Parent */ + if (stickydirmode(ad_p, DIRBITS | mode, dropbox, vol->v_umask) < 0) + return -1; + } + + if (ads_setfilmode(ad_dir(vol->vfs->ad_path( name, ADFLAGS_DIR)), mode, st, vol->v_umask) < 0) + return -1; + + if (!dir_rx_set(mode)) { + if (stickydirmode(ad_p, DIRBITS | mode, dropbox, vol->v_umask) < 0) + return -1 ; + if (stickydirmode(ad_dir(ad_p), DIRBITS | mode, dropbox, vol->v_umask) < 0) + return -1; + } + return 0; +} + +/* ------------------- */ +struct dir_mode { + mode_t mode; + int dropbox; +}; + +static int setdirmode_ads_loop(struct dirent *de _U_, char *name, void *data, int flag, mode_t v_umask) +{ + + struct dir_mode *param = data; + int ret = 0; /* 0 ignore error, -1 */ + + if (dir_rx_set(param->mode)) { + if (stickydirmode(name, DIRBITS | param->mode, param->dropbox, v_umask) < 0) { + if (flag) { + return 0; + } + return ret; + } + } + if (ads_setfilmode(name, param->mode, NULL, v_umask) < 0) + return ret; + + if (!dir_rx_set(param->mode)) { + if (stickydirmode(name, DIRBITS | param->mode, param->dropbox, v_umask) < 0) { + if (flag) { + return 0; + } + return ret; + } + } + return 0; +} + +static int RF_setdirmode_ads(const struct vol *vol, const char * name, mode_t mode, struct stat *st _U_) +{ + char *adouble = vol->vfs->ad_path( name, ADFLAGS_DIR ); + char ad_p[ MAXPATHLEN + 1]; + struct dir_mode param; + + param.mode = mode; + param.dropbox = vol->v_flags; + + strlcpy(ad_p,ad_dir(adouble), sizeof(ad_p)); + + if (dir_rx_set(mode)) { + /* .AppleDouble */ + if (stickydirmode(ad_dir(ad_p), DIRBITS | mode, param.dropbox, vol->v_umask) < 0) + return -1; + } + + if (for_each_adouble("setdirmode_ads", ad_dir(ad_p), setdirmode_ads_loop, ¶m, vol_noadouble(vol), vol->v_umask)) + return -1; + + if (!dir_rx_set(mode)) { + if (stickydirmode(ad_dir(ad_p), DIRBITS | mode, param.dropbox, vol->v_umask) < 0 ) + return -1; + } + return 0; +} + +/* ------------------- */ +static int setdirowner_ads1_loop(struct dirent *de _U_, char *name, void *data, int flag _U_, mode_t v_umask _U_) +{ + struct perm *owner = data; + + if ( chown( name, owner->uid, owner->gid ) < 0 && errno != EPERM ) { + LOG(log_debug, logtype_afpd, "setdirowner: chown %d/%d %s: %s", + owner->uid, owner->gid, fullpathname(name), strerror(errno) ); + /* return ( -1 ); Sometimes this is okay */ + } + return 0; +} + +static int setdirowner_ads_loop(struct dirent *de _U_, char *name, void *data, int flag, mode_t v_umask _U_) +{ + struct perm *owner = data; + + if (for_each_adouble("setdirowner", name, setdirowner_ads1_loop, data, flag, 0) < 0) + return -1; + + if ( chown( name, owner->uid, owner->gid ) < 0 && errno != EPERM ) { + LOG(log_debug, logtype_afpd, "setdirowner: chown %d/%d %s: %s", + owner->uid, owner->gid, fullpathname(name), strerror(errno) ); + /* return ( -1 ); Sometimes this is okay */ + } + return 0; +} + +static int RF_setdirowner_ads(const struct vol *vol, const char *name, uid_t uid, gid_t gid) +{ + int noadouble = vol_noadouble(vol); + char adouble_p[ MAXPATHLEN + 1]; + struct stat st; + struct perm owner; + + owner.uid = uid; + owner.gid = gid; + + strlcpy(adouble_p, ad_dir(vol->vfs->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; + + /* + * We cheat: we know that chown doesn't do anything. + */ + if ( stat( ".AppleDouble", &st ) < 0) { + if (errno == ENOENT && noadouble) + return 0; + LOG(log_error, logtype_afpd, "setdirowner: stat %s: %s", fullpathname(".AppleDouble"), strerror(errno) ); + return -1; + } + if ( gid && gid != st.st_gid && chown( ".AppleDouble", uid, gid ) < 0 && errno != EPERM ) { + LOG(log_debug, logtype_afpd, "setdirowner: chown %d/%d %s: %s", + uid, gid,fullpathname(".AppleDouble"), strerror(errno) ); + /* return ( -1 ); Sometimes this is okay */ + } + return 0; +} + +/* ------------------- */ +static int RF_deletefile_ads(const struct vol *vol, const char *file ) +{ + char *ad_p = ad_dir(vol->vfs->ad_path(file, ADFLAGS_HF )); + + return ads_delete_rf(ad_p); +} + +/* --------------------------- */ +int RF_renamefile_ads(const struct vol *vol, const char *src, const char *dst) +{ + 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) { + struct stat st; + + err = errno; + if (errno == ENOENT) { + struct adouble ad; + + if (stat(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. + * if we use the struct adouble passed in parameter it will not + * create .AppleDouble if the file is already opened, so we + * use a diff one, it's not a pb,ie it's not the same file, yet. + */ + 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); + + /* We must delete it */ + RF_deletefile_ads(vol, dst ); + if (!unix_rename( adsrc, ad_dir(vol->vfs->ad_path( dst, 0 ))) ) + err = 0; + else + err = errno; + } + else { /* it's something else, bail out */ + err = errno; + } + } + } + if (err) { + errno = err; + return -1; + } + return 0; +} + +/************************************************************************* + * osx adouble format + ************************************************************************/ +static int validupath_osx(const struct vol *vol, const char *name) +{ + return strncmp(name,"._", 2) && ( + (vol->v_flags & AFPVOL_USEDOTS) ? netatalk_name(name): name[0] != '.'); +} + +/* ---------------- */ +int RF_renamedir_osx(const struct vol *vol, const char *oldpath, const char *newpath) +{ + /* We simply move the corresponding ad file as well */ + char tempbuf[258]="._"; + return rename(vol->vfs->ad_path(oldpath,0),strcat(tempbuf,newpath)); +} + +/* ---------------- */ +int RF_deletecurdir_osx(const struct vol *vol) +{ + return netatalk_unlink( vol->vfs->ad_path(".",0) ); +} + +/* ---------------- */ +static int RF_setdirunixmode_osx(const struct vol *vol, const char * name, mode_t mode, struct stat *st) +{ + return adouble_setfilmode(vol->vfs->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_) +{ + return 0; +} + +/* ---------------- */ +static int +RF_setdirowner_osx(const struct vol *vol _U_, const char *path _U_, uid_t uid _U_, gid_t gid _U_) +{ + return 0; +} + +/* ---------------- */ +int RF_renamefile_osx(const struct vol *vol, const char *src, const char *dst) +{ + 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) { + struct stat st; + + err = errno; + if (errno == ENOENT && stat(adsrc, &st)) /* source has no ressource fork, */ + return 0; + errno = err; + return -1; + } + return 0; +} + +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 +}; + +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, +}; + +/* samba sfm format. ad_path shouldn't be set her */ +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, +}; + +/* ---------------- */ +void initvol_vfs(struct vol *vol) +{ + /* adouble stuff */ + if (vol->v_adouble == AD_VERSION2_OSX) { + vol->vfs = &netatalk_adouble_osx; + } + else if (vol->v_adouble == AD_VERSION1_SFM) { + vol->vfs = &netatalk_adouble_sfm; + } + else { + vol->vfs = &netatalk_adouble; + } + + /* 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 + } 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; + } +} + diff --git a/macros/summary.m4 b/macros/summary.m4 index ddcf2f00..78ffb3ea 100644 --- a/macros/summary.m4 +++ b/macros/summary.m4 @@ -1,4 +1,4 @@ -dnl $Id: summary.m4,v 1.5 2009-02-16 13:49:20 franklahm Exp $ +dnl $Id: summary.m4,v 1.6 2009-10-02 09:32:41 franklahm Exp $ dnl Autoconf macros, display configure summary AC_DEFUN([AC_NETATALK_CONFIG_SUMMARY], [ @@ -15,6 +15,7 @@ AC_DEFUN([AC_NETATALK_CONFIG_SUMMARY], [ if test "x$afp3" = "xyes"; then AC_MSG_RESULT([ Large file support (>2GB) for AFP3: $wx_largefile]) fi + AC_MSG_RESULT([ Extended Attributes: $neta_cv_eas]) AC_MSG_RESULT([ DDP enabled: $netatalk_cv_ddp_enabled]) AC_MSG_RESULT([ CNID:]) AC_MSG_RESULT([ backends: $compiled_backends]) @@ -61,7 +62,6 @@ dnl fi AC_MSG_RESULT([ force volume uid/gid: $netatalk_cv_force_uidgid]) AC_MSG_RESULT([ Apple 2 boot support: $compile_a2boot]) AC_MSG_RESULT([ ACL support: $neta_cv_nfsv4acl]) - AC_MSG_RESULT([ Extended Attributes: $neta_cv_extattrs]) if test x"$use_pam_so" = x"yes" -a x"$netatalk_cv_install_pam" = x"no"; then AC_MSG_RESULT([]) AC_MSG_WARN([ PAM support was configured for your system, but the netatalk PAM configuration file]) diff --git a/man/man5/AppleVolumes.default.5.tmpl b/man/man5/AppleVolumes.default.5.tmpl index 74e9c4ce..c24c0aa9 100644 --- a/man/man5/AppleVolumes.default.5.tmpl +++ b/man/man5/AppleVolumes.default.5.tmpl @@ -1,404 +1,587 @@ -.TH AppleVolumes.default 5 "06 September 2004" 2.0.0 Netatalk -.SH NAME -AppleVolumes.default \- Configuration file used by afpd(8) to determine the shares made available through Appletalk -.SH DESCRIPTION -\fB:ETCDIR:/AppleVolumes.default\fR is the -configuration file used by afpd to determine what -portions of the file system will be shared via Apple Filing Protocol, as -well as their behaviour. Any line not prefixed with # is interpreted. The -configuration lines are composed like: -.PP -\fBpath\fR \fI[ volume name ] [ options -]\fR -.PP -The path name must be a fully qualified path name, or a path name -using either the ~ shell shorthand or any of the substitution variables, -which are listed below. -.PP -The volume name is the name that appears in the Chooser ot the -"connect to server" dialog on Macintoshes to represent the appropriate -share. If there are spaces in the name, it should be in quotes (i.e. "File -Share"). The volume name may not exceed 27 characters in length, and -cannot contain the \fB':'\fR character. -.RS +'\" t +.\" Title: AppleVolumes.default +.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author] +.\" Generator: DocBook XSL Stylesheets v1.74.3 +.\" Date: 02 Octobre 2009 +.\" Manual: Netatalk 2.1 +.\" Source: Netatalk 2.1 +.\" Language: English +.\" +.TH "APPLEVOLUMES\&.DEFAU" "5" "02 Octobre 2009" "Netatalk 2.1" "Netatalk 2.1" +.\" ----------------------------------------------------------------- +.\" * set default formatting +.\" ----------------------------------------------------------------- +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l +.\" ----------------------------------------------------------------- +.\" * MAIN CONTENT STARTS HERE * +.\" ----------------------------------------------------------------- +.SH "NAME" +AppleVolumes.default \- Configuration file used by \fBafpd\fR(8) to determine the shares made available through Appletalk +.SH "DESCRIPTION" +.PP +:ETCDIR:/AppleVolumes\&.default +is the configuration file used by +\fBafpd\fR +to determine what portions of the file system will be shared via Apple Filing Protocol, as well as their behaviour\&. Any line not prefixed with # is interpreted\&. Newline escaping is supported\&. The configuration lines are composed like: +.PP +path +\fI[ volume name ] [ options ]\fR +.PP +The path name must be a fully qualified path name, or a path name using either the ~ shell shorthand or any of the substitution variables, which are listed below\&. +.PP +The volume name is the name that appears in the Chooser ot the "connect to server" dialog on Macintoshes to represent the appropriate share\&. If there are spaces in the name, it should be in quotes (i\&.e\&. "File Share")\&. The volume name may not exceed 27 characters in length, and cannot contain the +\':\' +character\&. +.if n \{\ +.sp +.\} +.RS 4 +.it 1 an-trap +.nr an-no-space-flag 1 +.nr an-break-flag 1 +.br +.ps +1 \fBNote\fR +.ps -1 +.br .PP -Each volume has to be configured on a \fBsingle\fR line. +Each volume has to be configured on a +\fBsingle\fR +line\&. Though newline escaping is supported\&. +.sp .5v .RE .PP The possible options and their meanings are: -.TP +.PP adouble:\fI[v1|v2|osx]\fR -specify the format of the metadata files, which are used for -saving Mac resource fork as well. Earlier versions used AppleDouble -V1, the new default format is V2. Starting with Netatalk 2.0, the -scheme MacOS X uses currently (10.3.x), is also supported -.RS +.RS 4 +Specify the format of the metadata files, which are used for saving Mac resource fork as well\&. Earlier versions used AppleDouble V1, the new default format is V2\&. Starting with Netatalk 2\&.0, the scheme MacOS X uses currently (10\&.3\&.x), is also supported +.if n \{\ +.sp +.\} +.RS 4 +.it 1 an-trap +.nr an-no-space-flag 1 +.nr an-break-flag 1 +.br +.ps +1 \fBNote\fR - -Using \fBadouble:osx\fR is \fBnot\fR recommended for production use. Its -only aim is to temporarely share eg. FAT32 formatted FireWire -harddrives written on a Macintosh with afpd. Apple's metadata -scheme lacks several essential features, so using it on the -server's side will break both CNIDs and MacOS 9 -compatibility -.RE -.TP +.ps -1 +.br +Using +\fBadouble:osx\fR +is +\fBnot\fR +recommended for production use\&. Its only aim is to temporarely share eg\&. FAT32 formatted FireWire harddrives written on a Macintosh with afpd\&. Apple\'s metadata scheme lacks several essential features, so using it on the server\'s side will break both CNIDs and MacOS 9 compatibility +.sp .5v +.RE +.RE +.PP allow:\fI[users/groups]\fR -The allow option allows the users and groups that access a -share to be specified. Users and groups are specified, delimited by -commas. Groups are designated by a @ prefix. Example: -allow:user1,user2,@group -.TP +.RS 4 +The allow option allows the users and groups that access a share to be specified\&. Users and groups are specified, delimited by commas\&. Groups are designated by a @ prefix\&. Example: allow:user1,user2,@group +.RE +.PP deny:\fI[users/groups]\fR -The deny option specifies users and groups who are not allowed -access to the share. It follows the same format as the allow -option. -.TP +.RS 4 +The deny option specifies users and groups who are not allowed access to the share\&. It follows the same format as the allow option\&. +.RE +.PP +allowed_hosts:\fI[IPv4 host address/IPv4 netmask bits[, \&.\&.\&. ]]\fR +.RS 4 +Only listed hosts and networks are allowed, all others are rejected\&. Example: allowed_hosts:10\&.1\&.0\&.0/16,10\&.2\&.1\&.100 +.RE +.PP +denied_hosts:\fI[IPv4 host address/IPv4 netmask bits[, \&.\&.\&.]]\fR +.RS 4 +Listed hosts and nets are rejected, all others are allowed\&. Example: denied_hosts: 192\&.168\&.100/24,10\&.1\&.1\&.1 +.RE +.PP cnidscheme:\fI[backend]\fR -set the CNID backend to be used for the volume, default is -[:DEFAULT_CNID_SCHEME:] available schemes: -[:COMPILED_BACKENDS:] -.TP +.RS 4 +set the CNID backend to be used for the volume, default is [:DEFAULT_CNID_SCHEME:] available schemes: [:COMPILED_BACKENDS:] +.RE +.PP dbpath:\fI[path]\fR -Sets the database information to be stored in path. You have -to specifiy a writable location, even if the volume is read -only. -.TP +.RS 4 +Sets the database information to be stored in path\&. You have to specifiy a writable location, even if the volume is read only\&. +.RE +.PP +ea:\fI[ad|solaris]\fR +.RS 4 +Specify the format of the metadata files which are used for saving Extended Attributes\&. +\fBad\fR +is the default and stores the EAs inside the +\&.AppleDouble +directories\&. +\fBsolaris\fR +uses native +\fI[Open]Solaris\fR +EAs if available\&. +.RE +.PP maccharset:\fI[charset]\fR -specifies the mac client codepage for this Volume, e.g. -"MAC_ROMAN", "MAC_CYRILLIC". If not specified the setting from -\fBafpd.conf\fR is inherited. This setting is only -required if you need volumes, where the mac codepage differs from -the one globally set in \fBafpd.conf\fR. -.TP +.RS 4 +specifies the mac client codepage for this Volume, e\&.g\&. "MAC_ROMAN", "MAC_CYRILLIC"\&. If not specified the setting from +afpd\&.conf +is inherited\&. This setting is only required if you need volumes, where the mac codepage differs from the one globally set in +afpd\&.conf\&. +.RE +.PP options:\fI[option]\fR -This allows multiple options to be specified in a comma -delimited format. The available options are: -.RS -.TP +.RS 4 +This allows multiple options to be specified in a comma delimited format\&. The available options are: +.PP +acls +.RS 4 +Enable ACLs on this volume\&. Requires a +\fINFSv4 ACLs\fR +compatible filesystem (e\&.g\&. ZFS) and an ACL API compatible to *Solaris\&. In other words: this requires Solaris, Opensolaris or a derived distribution\&. +.RE +.PP +tm +.RS 4 +Enable Time Machine suport for this volume\&. +.RE +.PP +invisibledots +.RS 4 +Use with +\fBusedots\fR: make dot files invisible\&. +.RE +.PP limitsize -Limit disk size reporting to 2GB. This can be used for -older Macintoshes using newer Appleshare clients. -.TP +.RS 4 +Limit disk size reporting to 2GB\&. This can be used for older Macintoshes using newer Appleshare clients\&. +.RE +.PP +preexec_close +.RS 4 +a non\-zero return code from preexec close the volume being immediately, preventing clients to mount/see the volume in question\&. +.RE +.PP ro -Specifies the share as being read only for all users. -The .AppleDB directory has to be writeable, you can use the -\fB\-dbpath\fR option to relocate it. -.TP -usedots -Don't do :hex translation for dot files. note: when this -option gets set, certain file names become illegal. These are -\&.Parent and anything that starts with .Apple. Also, dot files -created on the unix side are marked invisible. -.TP +.RS 4 +Specifies the share as being read only for all users\&. The \&.AppleDB directory has to be writeable, you can use the +\fB\-dbpath\fR +option to relocate it\&. +.RE +.PP root_preexec_close -a non\-zero return code from root_preexec closes the -volume immediately, preventing clients to mount/see the volume -in question. -.TP -preexec_close -a non\-zero return code from preexec close the volume -being immediately, preventing clients to mount/see the volume -in question. +.RS 4 +a non\-zero return code from root_preexec closes the volume immediately, preventing clients to mount/see the volume in question\&. +.RE +.PP +upriv +.RS 4 +use AFP3 unix privileges\&. Become familiar with the new "unix privileges" AFP permissions concepts in MacOS X before using this option\&. See also: +\fBperm|fperm|dperm\fR\&. +.RE +.PP +usedots +.RS 4 +Don\'t do :hex translation for dot files\&. note: when this option gets set, certain file names become illegal\&. These are \&.Parent and anything that starts with \&.Apple\&. See also +\fBinvisibledots\fR\&. +.RE .RE -.TP +.PP password:\fI[password]\fR -This option allows you to set a volume password, which can be -a maximum of 8 characters long (using ASCII strongly recommended at -the time of this writing). -.TP +.RS 4 +This option allows you to set a volume password, which can be a maximum of 8 characters long (using ASCII strongly recommended at the time of this writing)\&. +.RE +.PP +perm|fperm|dperm:[mode] +.RS 4 +Add(or) with the client requested permissions: +\fBperm\fR +affects files and directories, +\fBfperm\fR +is for files only, +\fBdperm\fR +is for directories only\&. Use with +\fBoptions:upriv\fR\&. +.PP +\fBExample.\ \&Volume for a collaborative workgroup\fR +.sp +.if n \{\ +.RS 4 +.\} +.nf +/path/to/volume "Workgroup" options:upriv dperm:0770 fperm:0660 +.fi +.if n \{\ +.RE +.\} +.RE +.PP preexec:\fI[command]\fR -command to be run when the volume is mounted, ignored for user -defined volumes -.TP +.RS 4 +command to be run when the volume is mounted, ignored for user defined volumes +.RE +.PP postexec:\fI[command]\fR -command to be run when the volume is closed, ignored for user -defined volumes -.TP +.RS 4 +command to be run when the volume is closed, ignored for user defined volumes +.RE +.PP root_preexec:\fI[command]\fR -command to be run as root when the volume is mounted, ignored -for user defined volumes -.TP +.RS 4 +command to be run as root when the volume is mounted, ignored for user defined volumes +.RE +.PP root_postexec:\fI[command]\fR -command to be run as root when the volume is closed, ignored -for user defined volumes -.TP +.RS 4 +command to be run as root when the volume is closed, ignored for user defined volumes +.RE +.PP rolist:[\fBusers/groups\fR] -Allows certain users and groups to have read\-only access to a -share. This follows the allow option format. -.TP +.RS 4 +Allows certain users and groups to have read\-only access to a share\&. This follows the allow option format\&. +.RE +.PP rwlist:\fI[users/groups]\fR -Allows certain users and groups to have read/write access to a -share. This follows the allow option format. -.TP +.RS 4 +Allows certain users and groups to have read/write access to a share\&. This follows the allow option format\&. +.RE +.PP veto:\fI[vetoed name]\fR -hide files and directories,where the path matches one of the -\&'/' delimited vetoed names. Matches are partial, e.g. path is -\fB/abc/def/file\fR and veto:/abc/ will hide the -file. -.TP +.RS 4 +hide files and directories,where the path matches one of the \'/\' delimited vetoed names\&. Matches are partial, e\&.g\&. path is +/abc/def/file +and veto:/abc/ will hide the file\&. +.RE +.PP volcharset:\fI[charset]\fR -specifies the volume codepage, e.g. "UTF8", "UTF8\-MAC", -"ISO\-8859\-15". Defaults to "UTF8". +.RS 4 +specifies the volume codepage, e\&.g\&. "UTF8", "UTF8\-MAC", "ISO\-8859\-15"\&. Defaults to "UTF8"\&. +.RE .SH "VARIABLE SUBSTITUTIONS" -You can use variables in both volume path and volume name. -.TP -1. -if you specify an unknown variable, it will not get -converted. -.TP -2. -if you specify a known variable, but that variable doesn't have -a value, it will get ignored. +.PP +You can use variables in both volume path and volume name\&. +.sp +.RS 4 +.ie n \{\ +\h'-04' 1.\h'+01'\c +.\} +.el \{\ +.sp -1 +.IP " 1." 4.2 +.\} +if you specify an unknown variable, it will not get converted\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04' 2.\h'+01'\c +.\} +.el \{\ +.sp -1 +.IP " 2." 4.2 +.\} +if you specify a known variable, but that variable doesn\'t have a value, it will get ignored\&. +.RE .PP The variables which can be used for substitutions are: -.TP +.PP $b +.RS 4 basename -.TP +.RE +.PP $c -client's ip or appletalk address -.TP +.RS 4 +client\'s ip or appletalk address +.RE +.PP $d +.RS 4 volume pathname on server -.TP +.RE +.PP $f -full name (contents of the gecos field in the passwd -file) -.TP +.RS 4 +full name (contents of the gecos field in the passwd file) +.RE +.PP $g +.RS 4 group name -.TP +.RE +.PP $h +.RS 4 hostname -.TP +.RE +.PP $i -client's ip, without port -.TP +.RS 4 +client\'s ip, without port +.RE +.PP $s +.RS 4 server name (this can be the hostname) -.TP +.RE +.PP $u -user name (if guest, it is the user that guest is running -as) -.TP +.RS 4 +user name (if guest, it is the user that guest is running as) +.RE +.PP $v +.RS 4 volume name (either ADEID_NAME or basename of path) -.TP +.RE +.PP $z +.RS 4 appletalk zone (may not exist) -.TP +.RE +.PP $$ +.RS 4 prints dollar sign ($) +.RE .PP -When using variable substitution in the volume name, always keep in -mind, not to exceed the 27 characters limit +When using variable substitution in the volume name, always keep in mind, not to exceed the 27 characters limit .PP -\fBUsing variable substitution when defining volumes\fR +\fBExample.\ \&Using variable substitution when defining volumes\fR .PP +.if n \{\ +.RS 4 +.\} .nf /home/groups/$g "Groupdir for $g" ~ "$f is the best one" .fi - -We define "groupdirs" for each primary -group and use a personalized server name for homedir shares. +.if n \{\ +.RE +.\} +.sp +We define "groupdirs" for each primary group and use a personalized server name for homedir shares\&. .SH "CNID BACKENDS" -The AFP protocol mostly refers to files and directories by ID and -not by name. Netatalk needs a way to store these ID's in a persistent way, -to achieve this several different CNID backends are available. The CNID -Databases are by default located in the \fB.AppleDB\fR -folder in the volume root. -.TP +.PP +The AFP protocol mostly refers to files and directories by ID and not by name\&. Netatalk needs a way to store these ID\'s in a persistent way, to achieve this several different CNID backends are available\&. The CNID Databases are by default located in the +\&.AppleDB +folder in the volume root\&. +.PP cdb -"Concurrent database", backend is based on Sleepycat's Berkely -DB. With this backend several afpd deamons access -the CNID database directly. Berkeley DB locking is used to -synchronize access, if more than one afpd process -is active for a volume. The drawback is, that the crash of a single -afpd process might corrupt the database. -.TP +.RS 4 +"Concurrent database", backend is based on Sleepycat\'s Berkely DB\&. With this backend several +\fBafpd\fR +deamons access the CNID database directly\&. Berkeley DB locking is used to synchronize access, if more than one +\fBafpd\fR +process is active for a volume\&. The drawback is, that the crash of a single +\fBafpd\fR +process might corrupt the database\&. +.RE +.PP dbd +.RS 4 Access to the CNID database is restricted to the -cnid_metad daemon process. -afpd processes communicate with the daemon for -database reads and updates. If built with Berkeley DB transactions -the probability for database corruption is practically zero, but -performance can be slower than with \fBcdb\fR -.TP +\fBcnid_metad\fR +daemon process\&. +\fBafpd\fR +processes communicate with the daemon for database reads and updates\&. If built with Berkeley DB transactions the probability for database corruption is practically zero, but performance can be slower than with +\fBcdb\fR +.RE +.PP last -This backend is an exception, in terms of ID persistency. ID's -are only valid for the current session. This is basically what -afpd did in the 1.5 (and 1.6) versions. This -backend is still available, as it is useful for e.g. sharing -cdroms. - +.RS 4 +This backend is an exception, in terms of ID persistency\&. ID\'s are only valid for the current session\&. This is basically what +\fBafpd\fR +did in the 1\&.5 (and 1\&.6) versions\&. This backend is still available, as it is useful for e\&.g\&. sharing cdroms\&. +.sp \fBWarning\fR: It is -\fINOT\fR recommended to use this backend for volumes -anymore, as afpd now relies heavily on a -persistent ID database. Aliases will likely not work and filename -mangling is not supported. -.PP -Even though ./configure \-\-help might show that -there are other CNID backends available, be warned those are likely broken -or mainly used for testing. Don't use them unless you know what you're -doing, they may be removed without further notice from future -versions. +\fINOT\fR +recommended to use this backend for volumes anymore, as +\fBafpd\fR +now relies heavily on a persistent ID database\&. Aliases will likely not work and filename mangling is not supported\&. +.RE +.PP +Even though +\fB\&./configure \-\-help\fR +might show that there are other CNID backends available, be warned those are likely broken or mainly used for testing\&. Don\'t use them unless you know what you\'re doing, they may be removed without further notice from future versions\&. .SH "CHARSET OPTIONS" -With OS X Apple introduced the AFP3 protocol. One of the most -important changes was that AFP3 uses unicode names encoded as UTF\-8 -decomposed. Previous AFP/OS versions used codepages, like MacRoman, -MacCentralEurope, etc. -.PP -afpd needs a way to preserve extended macintosh -characters, or characters illegal in unix filenames, when saving files on -a unix filesystem. Earlier versions used the the so called CAP encoding. -An extended character (>0x7F) would be converted to a :xx sequence, -e.g. the Apple Logo (MacRoman: 0XF0) was saved as \fB:f0\fR. -Some special characters will be converted as to :xx notation as well. -\&'\fB/\fR' will be encoded to \fB:2f\fR, if -\fB\-usedots\fR is not specified, a leading dot -\&'\fB.\fR' will be encoded as \fB:2e\fR. -.PP -This version now uses UTF\-8 as the default encoding for names. -Special characters, like '\fB/\fR' and a leading -\&'\fB.\fR' will still be CAP style encoded . -.PP -The \fB\-volcharset\fR option will allow you to select -another volume encoding. E.g. for western users another useful setting -could be \-volcharset ISO\-8859\-15. apfd will accept any -\fBiconv\fR(1) provided charset. If a character cannot be converted -from the mac codepage to the selected volcharset, afpd will save it as a -CAP encoded character. For AFP3 clients, afpd will -convert the UTF\-8 character to \fB\-maccharset\fR first. If this -conversion fails, you'll receive a \-50 error on the mac. -.PP -\fINote\fR: Whenever you can, please stick with the -default UTF\-8 volume format. +.PP +With OS X Apple introduced the AFP3 protocol\&. One of the most important changes was that AFP3 uses unicode names encoded as UTF\-8 decomposed\&. Previous AFP/OS versions used codepages, like MacRoman, MacCentralEurope, etc\&. +.PP +\fBafpd\fR +needs a way to preserve extended macintosh characters, or characters illegal in unix filenames, when saving files on a unix filesystem\&. Earlier versions used the the so called CAP encoding\&. An extended character (>0x7F) would be converted to a :xx sequence, e\&.g\&. the Apple Logo (MacRoman: 0XF0) was saved as +:f0\&. Some special characters will be converted as to :xx notation as well\&. \'/\' will be encoded to +:2f, if +\fB\-usedots\fR +is not specified, a leading dot \'\&.\' will be encoded as +:2e\&. +.PP +This version now uses UTF\-8 as the default encoding for names\&. Special characters, like \'/\' and a leading \'\&.\' will still be CAP style encoded \&. +.PP +The +\fB\-volcharset\fR +option will allow you to select another volume encoding\&. E\&.g\&. for western users another useful setting could be \-volcharset ISO\-8859\-15\&. +\fBapfd\fR +will accept any +\fBiconv\fR(1) +provided charset\&. If a character cannot be converted from the mac codepage to the selected volcharset, afpd will save it as a CAP encoded character\&. For AFP3 clients, +\fBafpd\fR +will convert the UTF\-8 +character to +\fB\-maccharset\fR +first\&. If this conversion fails, you\'ll receive a \-50 error on the mac\&. +.PP +\fINote\fR: Whenever you can, please stick with the default UTF\-8 volume format\&. .SH "COMPATIBILITY WITH EARLIER VERSIONS" -To use a volume created with an earlier afpd -version, you'll have to specify the following options: .PP -\fBuse a 1.x style volume\fR +To use a volume created with an earlier +\fBafpd\fR +version, you\'ll have to specify the following options: .PP +\fBExample.\ \&use a 1.x style volume\fR +.sp +.if n \{\ +.RS 4 +.\} .nf /path/to/volume "Volname" adouble:v1 volcharset:ASCII .fi +.if n \{\ +.RE +.\} .PP -In case you used an NLS you could try using a compatible iconv -charset for \fB\-volcharset\fR. -.PP -\fBuse a 1.x style volume, created with maccode.iso8859\-1\fR +In case you used an NLS you could try using a compatible iconv charset for +\fB\-volcharset\fR\&. .PP +\fBExample.\ \&use a 1.x style volume, created with maccode.iso8859-1\fR +.sp +.if n \{\ +.RS 4 +.\} .nf /path/to/volume "Volname" adouble:v1 volcharset:ISO\-8859\-1 .fi +.if n \{\ +.RE +.\} .PP -You should consider converting old style volumes to the new -UTF\-8/AD2 format. The safest way to do this, is to create a new volume -with the default options and copy the files between this volumes with a -mac. +You should consider converting old style volumes to the new UTF\-8/AD2 format\&. The safest way to do this, is to create a new volume with the default options and copy the files between this volumes with a mac\&. .PP -\fINote\fR: Using above example options will allow -you to downgrade to 1.x netatalk again. +\fINote\fR: Using above example options will allow you to downgrade to 1\&.x netatalk again\&. .PP -\fINote\fR: Some 1.x NLS files used non standard -mappings, e.g. \fBmaccode.iso8859\-1.adapted\fR. This is not -supported anymore. You'll have to copy the contents of those volumes files -to a Mac and then back to the netatalk server, preferably to an UTF\-8 -volume. +\fINote\fR: Some 1\&.x NLS files used non standard mappings, e\&.g\&. +maccode\&.iso8859\-1\&.adapted\&. Three 1\&.x CAP double\-byte maccharsets are incompatible to netatalk 2\&.x; "MAC_CHINESE_TRAD", "MAC_JAPANESE" and "MAC_KOREAN"\&. These are not supported anymore\&. You\'ll have to copy the contents of those volumes files to a Mac and then back to the netatalk server, preferably to an UTF\-8 volume\&. .SH "ADVANCED OPTIONS" -The following options should only be used after serious -consideration. Be sure you fully understood the, sometimes complex, -consequences, before using them. -.TP +.PP +The following options should only be used after serious consideration\&. Be sure you fully understood the, sometimes complex, consequences, before using them\&. +.PP casefold:\fB[option]\fR -The casefold option handles, if the case of filenames should -be changed. The available options are: - -\fBtolower\fR \- Lowercases names in both -directions. - -\fBtoupper\fR \- Uppercases names in both -directions. - -\fBxlatelower\fR \- Client sees lowercase, server -sees uppercase. - -\fBxlateupper\fR \- Client sees uppercase, server -sees lowercase. -.TP +.RS 4 +The casefold option handles, if the case of filenames should be changed\&. The available options are: +.sp +\fBtolower\fR +\- Lowercases names in both directions\&. +.sp +\fBtoupper\fR +\- Uppercases names in both directions\&. +.sp +\fBxlatelower\fR +\- Client sees lowercase, server sees uppercase\&. +.sp +\fBxlateupper\fR +\- Client sees uppercase, server sees lowercase\&. +.RE +.PP options:[\fBoption\fR] -This allows multiple options to be specified in a comma -delimited format. The available options are: -.RS -.TP +.RS 4 +This allows multiple options to be specified in a comma delimited format\&. The available options are: +.PP +nocnidcache +.RS 4 +If set +\fBafpd\fR +doesn\'t store the ID information in AppleDouble V2 header files\&. As these IDs are used for caching and as a database backup, this option normally shouldn\'t be set\&. +.RE +.PP crlf -Enables crlf translation for TEXT files, automatically -converting macintosh line breaks into Unix ones. Use of this -option might be dangerous since some older programs store -binary data files as type "TEXT" when saving and switch the -filetype in a second step. Afpd will -potentially destroy such files when "erroneously" changing -bytes in order to do line break translation. -.TP +.RS 4 +Enables crlf translation for TEXT files, automatically converting macintosh line breaks into Unix ones\&. Use of this option might be dangerous since some older programs store binary data files as type "TEXT" when saving and switch the filetype in a second step\&. +\fBAfpd\fR +will potentially destroy such files when "erroneously" changing bytes in order to do line break translation\&. +.RE +.PP dropbox -Allows a volume to be declared as being a "dropbox." -Note that netatalk must be compiled with dropkludge support -for this to function. \fIWarning\fR: This -option is deprecated and might not work as expected. -.TP +.RS 4 +Allows a volume to be declared as being a "dropbox\&." Note that netatalk must be compiled with dropkludge support for this to function\&. +\fIWarning\fR: This option is deprecated and might not work as expected\&. +.RE +.PP mswindows -Forces filename restrictions imposed by MS WinXX. -\fIWarning\fR: This is \fINOT\fR -recommened for volumes mainly used by Macs. Please make sure -you fully understand this option before using it. -.TP +.RS 4 +Forces filename restrictions imposed by MS WinXX\&. +\fIWarning\fR: This is +\fINOT\fR +recommened for volumes mainly used by Macs\&. Please make sure you fully understand this option before using it\&. +.if n \{\ +.sp +.\} +.RS 4 +.it 1 an-trap +.nr an-no-space-flag 1 +.nr an-break-flag 1 +.br +.ps +1 +\fBWarning\fR +.ps -1 +.br +This option breaks direct saving to netatalk volumes from some applications, i\&.e\&. OfficeX\&. +.sp .5v +.RE +.RE +.PP noadouble -Forces afpd to not create -\&.AppleDouble directories unless macintosh metadata needs to be -written. This option is only useful if you want to share files -mostly used NOT by macs, causing afpd to -not automatically create .AppleDouble subdirs containing AD -header files in every directory it enters (which will it do by -default). - -In case, you save or change files from mac clients, AD -metadata files have to be written even in case you set this -option. So you can't avoid the creation of .AppleDouble -directories and its contents when you give macs write access -to a share and they make use of it. - -Try to avoid \fBnoadouble\fR whenever -possible. -.TP +.RS 4 +Forces +\fBafpd\fR +to not create \&.AppleDouble directories unless macintosh metadata needs to be written\&. This option is only useful if you want to share files mostly used NOT by macs, causing +\fBafpd\fR +to not automatically create \&.AppleDouble subdirs containing AD header files in every directory it enters (which will it do by default)\&. +.sp +In case, you save or change files from mac clients, AD metadata files have to be written even in case you set this option\&. So you can\'t avoid the creation of \&.AppleDouble directories and its contents when you give macs write access to a share and they make use of it\&. +.sp +Try to avoid +\fBnoadouble\fR +whenever possible\&. +.RE +.PP nodev -always use 0 for device number, helps when the device -number is not constant across a reboot, cluster, ... -.TP +.RS 4 +always use 0 for device number, helps when the device number is not constant across a reboot, cluster, \&.\&.\&. +.RE +.PP nofileid -don't advertise createfileid, resolveid, deleteid -calls. -.TP +.RS 4 +don\'t advertise createfileid, resolveid, deleteid calls\&. +.RE +.PP nohex -Disables :hex translations for anything except dot -files. This option makes the \fB'/\fR' character -illegal. -.TP -prodos -Provides compatibility with Apple II clients. -.TP +.RS 4 +Disables :hex translations for anything except dot files\&. This option makes the +\'/\' character illegal\&. +.RE +.PP nostat -don't stat volume path when enumerating volumes list, -useful for automounting or volumes created by a preexec -script. -.TP -upriv -use AFP3 unix privileges. Become familiar with the new -"unix privileges" AFP permissions concepts in MacOS X before -using this option. +.RS 4 +don\'t stat volume path when enumerating volumes list, useful for automounting or volumes created by a preexec script\&. +.RE +.PP +prodos +.RS 4 +Provides compatibility with Apple II clients\&. +.RE .RE .SH "SEE ALSO" -\fBafpd.conf\fR(5), \fBafpd\fR(8) - +.PP +\fBafpd.conf\fR(5), +\fBafpd\fR(8) -- 2.39.2