]> arthur.barton.de Git - netatalk.git/blobdiff - libatalk/adouble/ad_open.c
Merge branch-2-1
[netatalk.git] / libatalk / adouble / ad_open.c
index da3bacf81258bc353c5f165c402b20f52fc10e05..cf957c6c2bad7c5075fbc27097248c37731f4fa3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $Id: ad_open.c,v 1.46 2009-09-11 13:26:05 franklahm Exp $
+ * $Id: ad_open.c,v 1.74 2010-04-13 08:05:06 franklahm Exp $
  *
  * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu)
  * Copyright (c) 1990,1991 Regents of The University of Michigan.
  *  +1-313-763-0525
  *  netatalk@itd.umich.edu
  *
- * NOTE: I don't use inline because a good compiler should be
- * able to optimize all the static below. Didier
+ */
+
+/*!
+ * @file
+ * Part of Netatalk's AppleDouble implementatation
+ * @note We don't use inlines because a good compiler should be
+ *       able to optimize all the static funcs below.
+ * @sa include/atalk/adouble.h
  */
 
 #ifdef HAVE_CONFIG_H
@@ -219,6 +225,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;
@@ -482,35 +490,6 @@ bail_err:
 }
 #endif /* AD_VERSION == AD_VERSION2 */
 
-/* --------------------------- */
-#ifdef ATACC
-mode_t ad_hf_mode (mode_t mode)
-{
-    /* we always need RW mode for file owner */
-#if 0
-    mode |= S_IRUSR;
-#endif
-    mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);
-    /* fnctl lock need write access */
-    if ((mode & S_IRUSR))
-        mode |= S_IWUSR;
-    if ((mode & S_IRGRP))
-        mode |= S_IWGRP;
-    if ((mode & S_IROTH))
-        mode |= S_IWOTH;
-    /* if write mode set add read mode */
-    if ((mode & S_IWUSR))
-        mode |= S_IRUSR;
-    if ((mode & S_IWGRP))
-        mode |= S_IRGRP;
-    if ((mode & S_IWOTH))
-        mode |= S_IROTH;
-
-    return mode;
-}
-
-#endif
-
 /* -------------------------------------
    read in the entries
 */
@@ -602,8 +581,8 @@ static int ad_header_read(struct adouble *ad, struct stat *hst)
                                        && (ad->ad_version != AD_VERSION2)
 #endif /* AD_VERSION == AD_VERSION2 */
             )) {
-        errno = EIO;
         LOG(log_debug, logtype_default, "ad_open: can't parse AppleDouble header.");
+        errno = EIO;
         return -1;
     }
 
@@ -620,8 +599,8 @@ static int ad_header_read(struct adouble *ad, struct stat *hst)
 
     buf += AD_HEADER_LEN;
     if (len > header_len - AD_HEADER_LEN) {
-        errno = EIO;
         LOG(log_debug, logtype_default, "ad_header_read: can't read entry info.");
+        errno = EIO;
         return -1;
     }
 
@@ -632,14 +611,14 @@ static int ad_header_read(struct adouble *ad, struct stat *hst)
     if (!ad_getentryoff(ad, ADEID_RFORK)
         || (ad_getentryoff(ad, ADEID_RFORK) > sizeof(ad->ad_data))
         ) {
-        errno = EIO;
         LOG(log_debug, logtype_default, "ad_header_read: problem with rfork entry offset.");
+        errno = EIO;
         return -1;
     }
 
     if (ad_getentryoff(ad, ADEID_RFORK) > header_len) {
-        errno = EIO;
         LOG(log_debug, logtype_default, "ad_header_read: can't read in entries.");
+        errno = EIO;
         return -1;
     }
 
@@ -787,35 +766,34 @@ static int ad_header_sfm_read(struct adouble *ad, struct stat *hst)
  * FIXME: should do something for pathname > MAXPATHLEN
  */
 char *
-ad_path( path, adflags )
-    const char  *path;
-    int     adflags;
+ad_path( const char *path, int adflags)
 {
     static char pathbuf[ MAXPATHLEN + 1];
-    char    c, *slash, buf[MAXPATHLEN + 1];
-    size_t      l;
+    const char *slash;
+    size_t  ;
 
-    l = strlcpy(buf, path, MAXPATHLEN +1);
     if ( adflags & ADFLAGS_DIR ) {
-        strcpy( pathbuf, buf);
-        if ( *buf != '\0' && l < MAXPATHLEN) {
+        l = strlcpy( pathbuf, path, sizeof(pathbuf));
+
+        if ( l && l < MAXPATHLEN) {
             pathbuf[l++] = '/';
-            pathbuf[l] = 0;
         }
-        slash = ".Parent";
+        strlcpy(pathbuf +l, ".AppleDouble/.Parent", sizeof(pathbuf) -l);
     } else {
-        if (NULL != ( slash = strrchr( buf, '/' )) ) {
-            c = *++slash;
-            *slash = '\0';
-            strcpy( pathbuf, buf);
-            *slash = c;
+        if (NULL != ( slash = strrchr( path, '/' )) ) {
+            slash++;
+            l = slash - path;
+            /* XXX we must return NULL here and test in the caller */
+            if (l > MAXPATHLEN)
+                l = MAXPATHLEN;
+            memcpy( pathbuf, path, l);
         } else {
-            pathbuf[ 0 ] = '\0';
-            slash = buf;
+            l = 0;
+            slash = path;
         }
+        l += strlcpy( pathbuf +l, ".AppleDouble/", sizeof(pathbuf) -l);
+        strlcpy( pathbuf + l, slash, sizeof(pathbuf) -l);
     }
-    strlcat( pathbuf, ".AppleDouble/", MAXPATHLEN +1);
-    strlcat( pathbuf, slash, MAXPATHLEN +1);
 
     return( pathbuf );
 }
@@ -884,9 +862,7 @@ static int ad_mkrf_osx(char *path _U_)
  *
  */
 char *
-ad_path_sfm( path, adflags )
-    const char  *path;
-    int     adflags;
+ad_path_sfm( const char *path, int adflags)
 {
     static char pathbuf[ MAXPATHLEN + 1];
     char    c, *slash, buf[MAXPATHLEN + 1];
@@ -965,41 +941,50 @@ static int ad_mkrf_sfm(char *path)
 #define DEFMASK 07700   /* be conservative */
 
 char
-*ad_dir(path)
-    const char      *path;
+*ad_dir(const char *path)
 {
     static char     modebuf[ MAXPATHLEN + 1];
     char        *slash;
-    size_t              len;
-
-    if ( (len = strlen( path )) >= MAXPATHLEN ) {
-        errno = ENAMETOOLONG;
-        return NULL;  /* can't do it */
-    }
-
     /*
      * For a path with directories in it, remove the final component
      * (path or subdirectory name) to get the name we want to stat.
      * For a path which is just a filename, use "." instead.
      */
-    strcpy( modebuf, path );
-    slash = strrchr( modebuf, '/' );
-    /* is last char a '/' */
-    if (slash && slash[1] == 0) {
-        while (modebuf < slash && slash[-1] == '/') {
-            --slash;
+    slash = strrchr( path, '/' );
+    if (slash) {
+        size_t len;
+
+        len = slash - path;
+        if (len >= MAXPATHLEN) {
+            errno = ENAMETOOLONG;
+            return NULL;  /* can't do it */
         }
-        if (modebuf < slash) {
+        memcpy( modebuf, path, len );
+        modebuf[len] = '\0';
+        /* is last char a '/' ? */
+        if (slash[1] == 0) {
+            slash = modebuf+ len;
+            /* remove them */
+            while (modebuf < slash && slash[-1] == '/') {
+                --slash;
+            }
+            if (modebuf == slash) {
+                goto use_cur;
+            }
+            *slash = '\0';
+            while (modebuf < slash && *slash != '/') {
+                --slash;
+            }
+            if (modebuf == slash) {
+                goto use_cur;
+            }
             *slash = '\0';      /* remove pathname component */
-            slash = strrchr( modebuf, '/' );
         }
+        return modebuf;
     }
-    if (slash) {
-        *slash = '\0';      /* remove pathname component */
-    } else {
-        modebuf[0] = '.';   /* use current directory */
-        modebuf[1] = '\0';
-    }
+use_cur:
+    modebuf[0] = '.';   /* use current directory */
+    modebuf[1] = '\0';
     return modebuf;
 }
 
@@ -1029,8 +1014,8 @@ int ad_stat(const char *path, struct stat *stbuf)
     if (!p) {
         return -1;
     }
-
-    return stat( p, stbuf );
+//FIXME!
+    return lstat( p, stbuf );
 }
 
 /* ----------------
@@ -1052,7 +1037,7 @@ static int ad_chown(const char *path, struct stat *stbuf)
     if (default_uid != (uid_t)-1) {
         /* we are root (admin) */
         id = (default_uid)?default_uid:stbuf->st_uid;
-        ret = chown( path, id, stbuf->st_gid );
+        ret = lchown( path, id, stbuf->st_gid );
     }
 #endif
     return ret;
@@ -1078,9 +1063,7 @@ static int ad_mode_st(const char *path, int *mode, struct stat *stbuf)
    return access right of path parent directory
 */
 int
-ad_mode( path, mode )
-    const char      *path;
-    int         mode;
+ad_mode( const char *path, int mode)
 {
     struct stat     stbuf;
     ad_mode_st(path, &mode, &stbuf);
@@ -1091,17 +1074,14 @@ ad_mode( path, mode )
  * Use mkdir() with mode bits taken from ad_mode().
  */
 int
-ad_mkdir( path, mode )
-    const char      *path;
-    int         mode;
+ad_mkdir( const char *path, int mode)
 {
     int ret;
     int st_invalid;
     struct stat stbuf;
 
-#ifdef DEBUG
-    LOG(log_info, logtype_default, "ad_mkdir: Creating directory with mode %d", mode);
-#endif /* DEBUG */
+    LOG(log_debug, logtype_default, "ad_mkdir: creating ad-directory '%s' with mode %04o",
+        path, mode);
 
     st_invalid = ad_mode_st(path, &mode, &stbuf);
     ret = mkdir( path, mode );
@@ -1203,28 +1183,72 @@ static struct adouble_fops ad_adouble = {
 
 void ad_init(struct adouble *ad, int flags, int options)
 {
-    memset( ad, 0, sizeof( struct adouble ) );
+    ad->ad_inited = 0;
     ad->ad_flags = flags;
     if (flags == AD_VERSION2_OSX) {
         ad->ad_ops = &ad_osx;
+        ad->ad_md = &ad->ad_resource_fork;
     }
     else if (flags == AD_VERSION1_SFM) {
         ad->ad_ops = &ad_sfm;
+        ad->ad_md = &ad->ad_metadata_fork;
     }
     else {
         ad->ad_ops = &ad_adouble;
+        ad->ad_md = &ad->ad_resource_fork;
     }
     ad->ad_options = options;
+
+    ad_data_fileno(ad) = -1;
+    ad_reso_fileno(ad) = -1;
+    ad_meta_fileno(ad) = -1;
+    /* following can be read even if there's no
+     * meda data.
+     */
+    memset(ad->ad_eid, 0, sizeof( ad->ad_eid ));
+    ad->ad_rlen = 0;
 }
 
-/* -------------------
- * It's not possible to open the header file O_RDONLY -- the read
- * will fail and return an error. this refcounts things now.
+/*!
+ * Open data-, metadata(header)- or ressource fork
+ *
+ * You must call ad_init() before ad_open, usually you'll just call it like this: \n
+ * @code
+ *      struct adoube ad;
+ *      ad_init(&ad, vol->v_adouble, vol->v_ad_options);
+ * @endcode
+ *
+ * @param path    Path to file or directory
+ *
+ * @param adflags ADFLAGS_DF: open data file/fork\n
+ *                ADFLAGS_HF: open header (metadata) file\n
+ *                ADFLAGS_RF: open ressource fork *** FIXME: not used ?! *** \n
+ *                ADFLAGS_CREATE: indicate creation\n
+ *                ADFLAGS_NOHF: it's not an error if header file couldn't be created\n
+ *                ADFLAGS_DIR: if path is a directory you MUST or ADFLAGS_DIR to adflags\n
+ *                ADFLAGS_NOADOUBLE: dont create adouble files if not necessary\n
+ *                ADFLAGS_RDONLY: open read only\n
+ *                ADFLAGS_OPENFORKS: check for open forks from other processes\n
+ *                ADFLAGS_MD: alias for ADFLAGS_HF\n
+ *                ADFLAGS_V1COMPAT: obsolete
+ *
+ * @param oflags  flags passed through to open syscall: \n
+ *                O_RDONLY: *** FIXME *** \n
+ *                O_RDWR: *** FIXME *** \n
+ *                O_CREAT: create fork\n
+ *                O_EXCL: fail if exists with O_CREAT
+ *
+ * @param mode    passed to open with O_CREAT
+ *
+ * @param ad      pointer to struct adouble
+ *
+ * @returns 0 on success
+ *
+ * @note It's not possible to open the header file O_RDONLY -- the read
+ *       will fail and return an error. this refcounts things now.\n
+ *       metadata(ressource)-fork only gets created with O_CREAT.
  */
-int ad_open( path, adflags, oflags, mode, ad )
-    const char      *path;
-    int         adflags, oflags, mode;
-    struct adouble  *ad;
+int ad_open( const char *path, int adflags, int oflags, int mode, struct adouble  *ad)
 {
     struct stat         st_dir;
     struct stat         st_meta;
@@ -1235,22 +1259,13 @@ int ad_open( path, adflags, oflags, mode, ad )
     int                 open_df = 0;
 
     if (ad->ad_inited != AD_INITED) {
-        ad_data_fileno(ad) = -1;
-        ad_reso_fileno(ad) = -1;
-        adf_lock_init(&ad->ad_data_fork);
-        adf_lock_init(&ad->ad_resource_fork);
-        if (ad->ad_flags != AD_VERSION1_SFM) {
-            ad->ad_md = &ad->ad_resource_fork;
-        }
-        else {
-            adf_lock_init(&ad->ad_metadata_fork);
-            ad->ad_md = &ad->ad_metadata_fork;
-            ad_meta_fileno(ad) = -1;
-        }
         ad->ad_inited = AD_INITED;
         ad->ad_refcount = 1;
         ad->ad_open_forks = 0;
         ad->ad_adflags = adflags;
+        ad->ad_resource_fork.adf_refcount = 0;
+        ad->ad_data_fork.adf_refcount = 0;
+        ad->ad_data_fork.adf_syml=0;
     }
     else {
         ad->ad_open_forks = ((ad->ad_data_fork.adf_refcount > 0) ? ATTRBIT_DOPEN : 0);
@@ -1269,14 +1284,30 @@ int ad_open( path, adflags, oflags, mode, ad )
                     admode = mode;
                 }
             }
-            ad->ad_data_fork.adf_fd =open( path, hoflags, admode );
-            if (ad->ad_data_fork.adf_fd < 0 ) {
+                
+            ad->ad_data_fork.adf_fd =open( path, hoflags | O_NOFOLLOW, admode );
+            
+            if (ad->ad_data_fork.adf_fd == -1) {
                 if ((errno == EACCES || errno == EROFS) && !(oflags & O_RDWR)) {
                     hoflags = oflags;
-                    ad->ad_data_fork.adf_fd = open( path, hoflags, admode );
+                    ad->ad_data_fork.adf_fd = open( path, hoflags | O_NOFOLLOW, admode );
+                }
+                if (ad->ad_data_fork.adf_fd == -1 && errno == OPEN_NOFOLLOW_ERRNO) {
+                    int lsz;
+
+                    ad->ad_data_fork.adf_syml = malloc(MAXPATHLEN+1);
+                    lsz = readlink(path, ad->ad_data_fork.adf_syml, MAXPATHLEN);
+                    if (lsz <= 0) {
+                        free(ad->ad_data_fork.adf_syml);
+                        return -1;
+                    }
+                    ad->ad_data_fork.adf_syml[lsz] = 0;
+                    ad->ad_data_fork.adf_syml = realloc(ad->ad_data_fork.adf_syml,lsz+1);
+                    ad->ad_data_fork.adf_fd = -2; /* -2 means its a symlink */
                 }
             }
-            if ( ad->ad_data_fork.adf_fd < 0)
+
+            if ( ad->ad_data_fork.adf_fd == -1 )
                 return -1;
 
             AD_SET(ad->ad_data_fork.adf_off);
@@ -1285,6 +1316,7 @@ int ad_open( path, adflags, oflags, mode, ad )
                 /* just created, set owner if admin (root) */
                 ad_chown(path, &st_dir);
             }
+            adf_lock_init(&ad->ad_data_fork);
         }
         else {
             /* the file is already open... but */
@@ -1322,21 +1354,25 @@ int ad_open( path, adflags, oflags, mode, ad )
             return -1;
         }
         ad_refresh(ad);
+        /* it's not new anymore */
+        ad->ad_md->adf_flags &= ~( O_TRUNC | O_CREAT );
         ad->ad_md->adf_refcount++;
         goto sfm;
     }
 
+    memset(ad->ad_eid, 0, sizeof( ad->ad_eid ));
+    ad->ad_rlen = 0;
     ad_p = ad->ad_ops->ad_path( path, adflags );
 
-    hoflags = oflags & ~O_CREAT;
+    hoflags = oflags & ~(O_CREAT | O_EXCL);
     if (!(adflags & ADFLAGS_RDONLY)) {
         hoflags = (hoflags & ~(O_RDONLY | O_WRONLY)) | O_RDWR;
     }
-    ad->ad_md->adf_fd = open( ad_p, hoflags, 0 );
+    ad->ad_md->adf_fd = open( ad_p, hoflags | O_NOFOLLOW, 0 );
     if (ad->ad_md->adf_fd < 0 ) {
         if ((errno == EACCES || errno == EROFS) && !(oflags & O_RDWR)) {
-            hoflags = oflags & ~O_CREAT;
-            ad->ad_md->adf_fd = open( ad_p, hoflags, 0 );
+            hoflags = oflags & ~(O_CREAT | O_EXCL);
+            ad->ad_md->adf_fd = open( ad_p, hoflags | O_NOFOLLOW, 0 );
         }
     }
 
@@ -1347,6 +1383,7 @@ int ad_open( path, adflags, oflags, mode, ad )
              * here.
              * if ((oflags & O_CREAT) ==> (oflags & O_RDWR)
              */
+            LOG(log_debug, logtype_default, "ad_open: creating new adouble file: %s", ad_p);
             admode = mode;
             errno = 0;
             st_invalid = ad_mode_st(ad_p, &admode, &st_dir);
@@ -1354,7 +1391,7 @@ int ad_open( path, adflags, oflags, mode, ad )
                 admode = mode;
             }
             admode = ad_hf_mode(admode);
-            if ( errno == ENOENT && !(adflags & ADFLAGS_NOADOUBLE) && ad->ad_flags != AD_VERSION2_OSX) {
+            if ((errno == ENOENT) && (ad->ad_flags != AD_VERSION2_OSX)) {
                 if (ad->ad_ops->ad_mkrf( ad_p) < 0) {
                     return ad_error(ad, adflags);
                 }
@@ -1379,16 +1416,23 @@ int ad_open( path, adflags, oflags, mode, ad )
         else {
             return ad_error(ad, adflags);
         }
-    } else if (fstat(ad->ad_md->adf_fd, &st_meta) == 0 && st_meta.st_size == 0) {
-        /* for 0 length files, treat them as new. */
-        ad->ad_md->adf_flags = hoflags| O_TRUNC;
     } else {
         ad->ad_md->adf_flags = hoflags;
+        if (fstat(ad->ad_md->adf_fd, &st_meta) == 0 && st_meta.st_size == 0) {
+            /* for 0 length files, treat them as new. */
+            ad->ad_md->adf_flags |= O_TRUNC;
+        } 
+        else {
+            /* we have valid data in st_meta stat structure, reused it
+               in ad_header_read
+            */
+            pst = &st_meta;
+        }
     }
     AD_SET(ad->ad_md->adf_off);
 
-    memset(ad->ad_eid, 0, sizeof( ad->ad_eid ));
-    ad->ad_md->adf_refcount++;
+    ad->ad_md->adf_refcount = 1;
+    adf_lock_init(ad->ad_md);
     if ((ad->ad_md->adf_flags & ( O_TRUNC | O_CREAT ))) {
         /*
          * This is a new adouble header file. Initialize the structure,
@@ -1443,8 +1487,6 @@ sfm:
 
     ad_p = ad->ad_ops->ad_path( path, ADFLAGS_RF );
 
-    hoflags = (oflags & ~(O_RDONLY | O_WRONLY)) | O_RDWR;
-    ad->ad_resource_fork.adf_fd = open( ad_p, hoflags, admode );
     admode = mode;
     st_invalid = ad_mode_st(ad_p, &admode, &st_dir);
 
@@ -1452,6 +1494,9 @@ sfm:
         admode = mode;
     }
 
+    hoflags = (oflags & ~(O_RDONLY | O_WRONLY)) | O_RDWR;
+    ad->ad_resource_fork.adf_fd = open( ad_p, hoflags, admode );
+
     if (ad->ad_resource_fork.adf_fd < 0 ) {
         if ((errno == EACCES || errno == EROFS) && !(oflags & O_RDWR)) {
             hoflags = oflags;
@@ -1466,7 +1511,7 @@ sfm:
         errno = err;
         return -1;
     }
-
+    adf_lock_init(&ad->ad_resource_fork);
     AD_SET(ad->ad_resource_fork.adf_off);
     ad->ad_resource_fork.adf_flags = hoflags;
     if ((oflags & O_CREAT) && !st_invalid) {
@@ -1479,18 +1524,36 @@ sfm:
     return 0 ;
 }
 
-/* -----------------------------------
- * return only metadata but try very hard
+/*!
+ * @brief open metadata, possibly as root
+ *
+ * Return only metadata but try very hard ie at first try as user, then try as root.
+ *
+ * @param name  name of file/dir
+ * @param flags ADFLAGS_DIR: name is a directory \n
+ *              ADFLAGS_CREATE: force creation of header file, but only as user, not as root\n
+ *              ADFLAGS_OPENFORKS: test if name is open by another afpd process
+ *
+ * @param adp   pointer to struct adouble
+ *
+ * @note caller MUST pass ADFLAGS_DIR for directories. Whether ADFLAGS_CREATE really creates
+ *       a adouble file depends on various other volume options, eg. ADVOL_CACHE
  */
 int ad_metadata(const char *name, int flags, struct adouble *adp)
 {
     uid_t uid;
-    int   ret, err;
-    int   dir = flags & ADFLAGS_DIR;
+    int   ret, err, dir;
+    int   create = O_RDONLY;
 
-    /* Open with O_CREAT, thus enumarating a dir will create missing adouble files, see: */
-    /* http://marc.info/?l=netatalk-devel&m=124039156832408&w=2 */
-    if ((ret = ad_open(name, ADFLAGS_HF | dir, O_RDWR | O_CREAT, 0666, adp)) < 0 && errno == EACCES) {
+    dir = flags & ADFLAGS_DIR;
+
+    /* Check if we shall call ad_open with O_CREAT */
+    if ( (adp->ad_options & ADVOL_CACHE)
+         && ! (adp->ad_options & ADVOL_NOADOUBLE)
+         && (flags & ADFLAGS_CREATE) ) {
+        create = O_CREAT | O_RDWR;
+    }
+    if ((ret = ad_open(name, ADFLAGS_HF | dir, create, 0666, adp)) < 0 && errno == EACCES) {
         uid = geteuid();
         if (seteuid(0)) {
             LOG(log_error, logtype_default, "ad_metadata(%s): seteuid failed %s", name, strerror(errno));
@@ -1508,27 +1571,50 @@ int ad_metadata(const char *name, int flags, struct adouble *adp)
     }
 
     if (!ret && (ADFLAGS_OPENFORKS & flags)) {
-        u_int16_t attrbits = adp->ad_open_forks;
-
         /*
           we need to check if the file is open by another process.
           it's slow so we only do it if we have to:
           - it's requested.
           - we don't already have the answer!
         */
+        adp->ad_open_forks |= ad_openforks(adp, adp->ad_open_forks);
+    }
+    return ret;
+}
+
+/*
+ * @brief openat like wrapper for ad_metadata
+ */
+int ad_metadataat(int dirfd, const char *name, int flags, struct adouble *adp)
+{
+    int ret = 0;
+    int cwdfd = -1;
 
-        if (!(attrbits & ATTRBIT_ROPEN)) {
-            attrbits |= ad_testlock(adp, ADEID_RFORK, AD_FILELOCK_OPEN_RD) > 0? ATTRBIT_ROPEN : 0;
-            attrbits |= ad_testlock(adp, ADEID_RFORK, AD_FILELOCK_OPEN_WR) > 0? ATTRBIT_ROPEN : 0;
+    if (dirfd != -1) {
+        if ((cwdfd = open(".", O_RDONLY) == -1) || (fchdir(dirfd) != 0)) {
+            ret = -1;
+            goto exit;
         }
+    }
 
-        if (!(attrbits & ATTRBIT_DOPEN)) {
-            attrbits |= ad_testlock(adp, ADEID_DFORK, AD_FILELOCK_OPEN_RD) > 0? ATTRBIT_DOPEN : 0;
-            attrbits |= ad_testlock(adp, ADEID_DFORK, AD_FILELOCK_OPEN_WR) > 0? ATTRBIT_DOPEN : 0;
+    if (ad_metadata(name, flags, adp) < 0) {
+        ret = -1;
+        goto exit;
+    }
+
+    if (dirfd != -1) {
+        if (fchdir(cwdfd) != 0) {
+            LOG(log_error, logtype_afpd, "ad_openat: cant chdir back, exiting");
+            exit(EXITERR_SYS);
         }
-        adp->ad_open_forks = attrbits;
     }
+
+exit:
+    if (cwdfd != -1)
+        close(cwdfd);
+
     return ret;
+
 }
 
 /* ----------------------------------- */
@@ -1588,7 +1674,7 @@ static int new_rfork(const char *path, struct adouble *ad, int adflags)
         memcpy(ad_entry(ad, ADEID_FINDERI) + FINDERINFO_FRFLAGOFF, &ashort, sizeof(ashort));
     }
 
-    if (stat(path, &st) < 0) {
+    if (lstat(path, &st) < 0) {
         return -1;
     }
 
@@ -1610,3 +1696,39 @@ int ad_refresh(struct adouble *ad)
 
     return ad->ad_ops->ad_header_read(ad, NULL);
 }
+
+int ad_openat(int dirfd,  /* dir fd openat like */
+              const char *path,
+              int adflags,
+              int oflags,
+              int mode,
+              struct adouble  *ad)
+{
+    int ret = 0;
+    int cwdfd = -1;
+
+    if (dirfd != -1) {
+        if ((cwdfd = open(".", O_RDONLY) == -1) || (fchdir(dirfd) != 0)) {
+            ret = -1;
+            goto exit;
+        }
+    }
+
+    if (ad_open(path, adflags, oflags, mode, ad) < 0) {
+        ret = -1;
+        goto exit;
+    }
+
+    if (dirfd != -1) {
+        if (fchdir(cwdfd) != 0) {
+            LOG(log_error, logtype_afpd, "ad_openat: cant chdir back, exiting");
+            exit(EXITERR_SYS);
+        }
+    }
+
+exit:
+    if (cwdfd != -1)
+        close(cwdfd);
+
+    return ret;
+}