/*
 * See COPYRIGHT.
 *
 * bug:
 * afp_XXXcomment are (the only) functions able to open
 * a resource fork when there's no data fork, e.g. after
 * it was removed with samba.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <netatalk/at.h>

#include <atalk/adouble.h>
#include <atalk/afp.h>
#include <atalk/atp.h>
#include <atalk/asp.h>
#include <atalk/bstradd.h>
#include <atalk/bstrlib.h>
#include <atalk/dsi.h>
#include <atalk/errchk.h>
#include <atalk/globals.h>
#include <atalk/logger.h>
#include <atalk/netatalk_conf.h>
#include <atalk/unix.h>
#include <atalk/util.h>

#include "desktop.h"
#include "directory.h"
#include "fork.h"
#include "mangle.h"
#include "volume.h"

#define EXEC_MODE (S_IXGRP | S_IXUSR | S_IXOTH)

int setdeskmode(const struct vol *vol, const mode_t mode)
{
    EC_INIT;
    char		wd[ MAXPATHLEN + 1];
    struct stat         st;
    char		modbuf[ 12 + 1], *m;
    struct dirent	*deskp, *subp;
    DIR			*desk, *sub;

    if (!dir_rx_set(mode)) {
        /* want to remove read and search access to owner it will screw the volume */
        return -1 ;
    }
    if ( getcwd( wd , MAXPATHLEN) == NULL ) {
        return -1;
    }

    bstring dtpath = bfromcstr(vol->v_dbpath);
    bcatcstr(dtpath, "/" APPLEDESKTOP);

    EC_NEG1( chdir(cfrombstr(dtpath)) );

    if (( desk = opendir( "." )) == NULL ) {
        if ( chdir( wd ) < 0 ) {
            LOG(log_error, logtype_afpd, "setdeskmode: chdir %s: %s", wd, strerror(errno) );
        }
        EC_FAIL;
    }
    for ( deskp = readdir( desk ); deskp != NULL; deskp = readdir( desk )) {
        if ( strcmp( deskp->d_name, "." ) == 0 ||
                strcmp( deskp->d_name, ".." ) == 0 || strlen( deskp->d_name ) > 2 ) {
            continue;
        }
        strcpy( modbuf, deskp->d_name );
        strcat( modbuf, "/" );
        m = strchr( modbuf, '\0' );
        if (( sub = opendir( deskp->d_name )) == NULL ) {
            continue;
        }
        for ( subp = readdir( sub ); subp != NULL; subp = readdir( sub )) {
            if ( strcmp( subp->d_name, "." ) == 0 ||
                    strcmp( subp->d_name, ".." ) == 0 ) {
                continue;
            }
            *m = '\0';
            strcat( modbuf, subp->d_name );
            /* XXX: need to preserve special modes */
            if (lstat(modbuf, &st) < 0) {
                LOG(log_error, logtype_afpd, "setdeskmode: stat %s: %s",fullpathname(modbuf), strerror(errno) );
                continue;
            }

            if (S_ISDIR(st.st_mode)) {
                if (ochmod(modbuf,
                           DIRBITS | mode,
                           &st,
                           vol_syml_opt(vol) | vol_chmod_opt(vol)
                        ) < 0 && errno != EPERM) {
                     LOG(log_error, logtype_afpd, "setdeskmode: chmod %s: %s",fullpathname(modbuf), strerror(errno) );
                }
            } else if (ochmod(modbuf,
                           mode & ~EXEC_MODE,
                           &st,
                           vol_syml_opt(vol) | vol_chmod_opt(vol)
                        ) < 0 && errno != EPERM) {
                LOG(log_error, logtype_afpd, "setdeskmode: chmod %s: %s",fullpathname(modbuf), strerror(errno) );
            }

        }
        closedir( sub );
        /* XXX: need to preserve special modes */
        if (ochmod(deskp->d_name,
                   DIRBITS | mode,
                   NULL,
                   vol_syml_opt(vol) | vol_chmod_opt(vol)
                ) < 0 && errno != EPERM) {
            LOG(log_error, logtype_afpd, "setdeskmode: chmod %s: %s",fullpathname(deskp->d_name), strerror(errno) );
        }
    }
    closedir( desk );
    if ( chdir( wd ) < 0 ) {
        LOG(log_error, logtype_afpd, "setdeskmode: chdir %s: %s", wd, strerror(errno) );
        EC_FAIL;
    }
    /* XXX: need to preserve special modes */
    if (ochmod(bdata(dtpath),
               DIRBITS | mode,
               NULL,
               vol_syml_opt(vol) | vol_chmod_opt(vol)
            ) < 0 && errno != EPERM) {
        LOG(log_error, logtype_afpd, "setdeskmode: chmod %s: %s", bdata(dtpath), strerror(errno));
    }

EC_CLEANUP:
    bdestroy(dtpath);
    EC_EXIT;
}

int setdeskowner(const struct vol *vol, uid_t uid, gid_t gid)
{
    EC_INIT;
    char		wd[ MAXPATHLEN + 1];
    char		modbuf[12 + 1], *m;
    struct dirent	*deskp, *subp;
    DIR			*desk, *sub;

    if ( getcwd( wd, MAXPATHLEN ) == NULL ) {
        return -1;
    }

    bstring dtpath = bfromcstr(vol->v_dbpath);
    bcatcstr(dtpath, "/" APPLEDESKTOP);

    EC_NEG1( chdir(cfrombstr(dtpath)) );

    if (( desk = opendir( "." )) == NULL ) {
        if ( chdir( wd ) < 0 ) {
            LOG(log_error, logtype_afpd, "setdeskowner: chdir %s: %s", wd, strerror(errno) );
        }
        EC_FAIL;
    }
    for ( deskp = readdir( desk ); deskp != NULL; deskp = readdir( desk )) {
        if ( strcmp( deskp->d_name, "." ) == 0 ||
                strcmp( deskp->d_name, ".." ) == 0 ||
                strlen( deskp->d_name ) > 2 ) {
            continue;
        }
        strcpy( modbuf, deskp->d_name );
        strcat( modbuf, "/" );
        m = strchr( modbuf, '\0' );
        if (( sub = opendir( deskp->d_name )) == NULL ) {
            continue;
        }
        for ( subp = readdir( sub ); subp != NULL; subp = readdir( sub )) {
            if ( strcmp( subp->d_name, "." ) == 0 ||
                    strcmp( subp->d_name, ".." ) == 0 ) {
                continue;
            }
            *m = '\0';
            strcat( modbuf, subp->d_name );
            /* XXX: add special any uid, ignore group bits */
            if ( chown( modbuf, uid, gid ) < 0 && errno != EPERM ) {
                LOG(log_error, logtype_afpd, "setdeskown: chown %s: %s", fullpathname(modbuf), strerror(errno) );
            }
        }
        closedir( sub );
        /* XXX: add special any uid, ignore group bits */
        if ( chown( deskp->d_name, uid, gid ) < 0 && errno != EPERM ) {
            LOG(log_error, logtype_afpd, "setdeskowner: chown %s: %s",
                deskp->d_name, strerror(errno) );
        }
    }
    closedir( desk );
    if ( chdir( wd ) < 0 ) {
        LOG(log_error, logtype_afpd, "setdeskowner: chdir %s: %s", wd, strerror(errno) );
        EC_FAIL;
    }
    if (chown(cfrombstr(dtpath), uid, gid ) < 0 && errno != EPERM ) {
        LOG(log_error, logtype_afpd, "setdeskowner: chown %s: %s", fullpathname(".AppleDouble"), strerror(errno) );
    }

EC_CLEANUP:
    bdestroy(dtpath);
    EC_EXIT;
}

static void create_appledesktop_folder(const struct vol * vol)
{
    bstring olddtpath = NULL, dtpath = NULL;
    struct stat st;

    olddtpath = bfromcstr(vol->v_path);
    bcatcstr(olddtpath, "/" APPLEDESKTOP);

    dtpath = bfromcstr(vol->v_dbpath);
    bcatcstr(dtpath, "/" APPLEDESKTOP);

    become_root();

    if (lstat(cfrombstr(dtpath), &st) != 0) {
        if ((lstat(cfrombstr(olddtpath), &st) == 0) && (S_ISDIR(st.st_mode) != 0)) {
	    if (rename(bdata(olddtpath), bdata(dtpath)) != 0) {
                LOG(log_error, logtype_afpd, "moving .AppleDesktop from \"%s\" failed; creating new dir \"%s\"",
                    bdata(olddtpath), bdata(dtpath));
                mkdir(cfrombstr(dtpath), 0777);
            }
        } else {
            LOG(log_debug, logtype_afpd, "no valid .AppleDesktop dir found; creating new dir \"%s\"",
                bdata(dtpath));
            mkdir(cfrombstr(dtpath), 0777);
        }
    }

    unbecome_root();

    bdestroy(dtpath);
    bdestroy(olddtpath);
}

int afp_opendt(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *rbuflen)
{
    struct vol	*vol;
    uint16_t	vid;

    ibuf += 2;

    memcpy( &vid, ibuf, sizeof(vid));
    if (NULL == ( vol = getvolbyvid( vid )) ) {
        *rbuflen = 0;
        return AFPERR_PARAM;
    }

    create_appledesktop_folder(vol);

    memcpy( rbuf, &vid, sizeof(vid));
    *rbuflen = sizeof(vid);
    return AFP_OK;
}

int afp_closedt(AFPObj *obj _U_, char *ibuf _U_, size_t ibuflen _U_, char *rbuf _U_, size_t *rbuflen)
{
    *rbuflen = 0;
    return AFP_OK;
}

static struct savedt	si = { { 0, 0, 0, 0 }, -1, 0, 0 };

static char *icon_dtfile(struct vol *vol, uint8_t creator[ 4 ])
{
    return dtfile( vol, creator, ".icon" );
}

static int iconopen(struct vol *vol, uint8_t creator[ 4 ], int flags, int mode)
{
    char	*dtf, *adt, *adts;

    if ( si.sdt_fd != -1 ) {
        if ( memcmp( si.sdt_creator, creator, sizeof( CreatorType )) == 0 &&
                si.sdt_vid == vol->v_vid ) {
            return 0;
        }
        close( si.sdt_fd );
        si.sdt_fd = -1;
    }

    dtf = icon_dtfile( vol, creator);

    if (( si.sdt_fd = open( dtf, flags, ad_mode( dtf, mode ))) < 0 ) {
        if ( errno == ENOENT && ( flags & O_CREAT )) {
            if (( adts = strrchr( dtf, '/' )) == NULL ) {
                return -1;
            }
            *adts = '\0';
            if (( adt = strrchr( dtf, '/' )) == NULL ) {
                return -1;
            }
            *adt = '\0';
            (void) ad_mkdir( dtf, DIRBITS | 0777 );
            *adt = '/';
            (void) ad_mkdir( dtf, DIRBITS | 0777 );
            *adts = '/';

            if (( si.sdt_fd = open( dtf, flags, ad_mode( dtf, mode ))) < 0 ) {
                LOG(log_error, logtype_afpd, "iconopen(%s): open: %s", dtf, strerror(errno) );
                return -1;
            }
        } else {
            return -1;
        }
    }

    memcpy( si.sdt_creator, creator, sizeof( CreatorType ));
    si.sdt_vid = vol->v_vid;
    si.sdt_index = 1;
    return 0;
}

int afp_addicon(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *rbuflen)
{
    struct vol* vol;
#ifndef NO_DDP
    struct iovec	iov[2];
#endif
    uint8_t		fcreator[4];
    uint8_t		imh[12];
    uint8_t		irh[12];
    uint8_t		* p;
    int			itype;
    int         cc = AFP_OK;
    int         iovcnt = 0;
    size_t 		buflen;
    uint32_t    ftype;
    uint32_t    itag;
    uint16_t		bsize = 0;
    uint16_t		rsize;
    uint16_t		vid;

    buflen = *rbuflen;
    *rbuflen = 0;
    ibuf += 2;

    memcpy( &vid, ibuf, sizeof( vid ));
    ibuf += sizeof( vid );
    if (NULL == ( vol = getvolbyvid( vid )) ) {
        cc = AFPERR_PARAM;
        goto addicon_err;
    }

    memcpy( fcreator, ibuf, sizeof( fcreator ));
    ibuf += sizeof( fcreator );

    memcpy( &ftype, ibuf, sizeof( ftype ));
    ibuf += sizeof( ftype );

    itype = (unsigned char) *ibuf;
    ibuf += 2;

    memcpy( &itag, ibuf, sizeof( itag ));
    ibuf += sizeof( itag );

    memcpy( &bsize, ibuf, sizeof( bsize ));
    bsize = ntohs( bsize );

    if ( si.sdt_fd != -1 ) {
        (void)close( si.sdt_fd );
        si.sdt_fd = -1;
    }

    if (iconopen( vol, fcreator, O_RDWR|O_CREAT, 0666 ) < 0) {
        cc = AFPERR_NOITEM;
        goto addicon_err;
    }

    if (lseek( si.sdt_fd, (off_t) 0L, SEEK_SET ) < 0) {
        close(si.sdt_fd);
        si.sdt_fd = -1;
        LOG(log_error, logtype_afpd, "afp_addicon(%s): lseek: %s", icon_dtfile(vol, fcreator), strerror(errno) );
        cc = AFPERR_PARAM;
        goto addicon_err;
    }

    /*
     * Read icon elements until we find a match to replace, or
     * we get to the end to insert.
     */
    p = imh;
    memcpy( p, &itag, sizeof( itag ));
    p += sizeof( itag );
    memcpy( p, &ftype, sizeof( ftype ));
    p += sizeof( ftype );
    *p++ = itype;
    *p++ = 0;
    bsize = htons( bsize );
    memcpy( p, &bsize, sizeof( bsize ));
    bsize = ntohs( bsize );
    while (( cc = read( si.sdt_fd, irh, sizeof( irh ))) > 0 ) {
        memcpy( &rsize, irh + 10, sizeof( rsize ));
        rsize = ntohs( rsize );
        /*
         * Is this our set of headers?
         */
        if ( memcmp( irh, imh, sizeof( irh ) - sizeof( u_short )) == 0 ) {
            /*
             * Is the size correct?
             */
            if ( bsize != rsize )
                cc = AFPERR_ITYPE;
            break;
        }

        if ( lseek( si.sdt_fd, (off_t) rsize, SEEK_CUR ) < 0 ) {
            LOG(log_error, logtype_afpd, "afp_addicon(%s): lseek: %s", icon_dtfile(vol, fcreator),strerror(errno) );
            cc = AFPERR_PARAM;
        }
    }

    /*
     * Some error occurred, return.
     */
addicon_err:
    if ( cc < 0 ) {
        if (obj->proto == AFPPROTO_DSI) {
            dsi_writeinit(obj->dsi, rbuf, buflen);
            dsi_writeflush(obj->dsi);
        }
    }

    switch (obj->proto) {
#ifndef NO_DDP
    case AFPPROTO_ASP:
        buflen = bsize;
        if ((asp_wrtcont(obj->handle, rbuf, &buflen) < 0) || buflen != bsize)
            return AFPERR_PARAM;

        /*
         * We're at the end of the file, add the headers, etc.  */
        if (cc == 0) {
            iov[0].iov_base = (caddr_t)imh;
            iov[0].iov_len = sizeof(imh);
            iov[1].iov_base = rbuf;
            iov[1].iov_len = bsize;
            iovcnt = 2;
        }

        /*
         * We found an icon to replace.
         */
        if (cc > 0) {
            iov[0].iov_base = rbuf;
            iov[0].iov_len = bsize;
            iovcnt = 1;
        }

        if (writev(si.sdt_fd, iov, iovcnt) < 0) {
            LOG(log_error, logtype_afpd, "afp_addicon(%s): writev: %s", icon_dtfile(vol, fcreator), strerror(errno));
            return AFPERR_PARAM;
        }
        break;
#endif /* no afp/asp */
    case AFPPROTO_DSI:
    {
        DSI* dsi = obj->dsi;

        iovcnt = dsi_writeinit(dsi, rbuf, buflen);

        /* add headers at end of file */
        if ((cc == 0) && (write(si.sdt_fd, imh, sizeof(imh)) < 0)) {
            LOG(log_error, logtype_afpd, "afp_addicon(%s): write: %s", icon_dtfile(vol, fcreator), strerror(errno));
            dsi_writeflush(dsi);
            return AFPERR_PARAM;
        }

        if ((cc = write(si.sdt_fd, rbuf, iovcnt)) < 0) {
            LOG(log_error, logtype_afpd, "afp_addicon(%s): write: %s", icon_dtfile(vol, fcreator), strerror(errno));
            dsi_writeflush(dsi);
            return AFPERR_PARAM;
        }

        while ((iovcnt = dsi_write(dsi, rbuf, buflen))) {
            if ((cc = write(si.sdt_fd, rbuf, iovcnt)) < 0) {
                LOG(log_error, logtype_afpd, "afp_addicon(%s): write: %s", icon_dtfile(vol, fcreator), strerror(errno));
                dsi_writeflush(dsi);
                return AFPERR_PARAM;
            }
        }
    }
    break;
    }

    close( si.sdt_fd );
    si.sdt_fd = -1;
    return AFP_OK;
}

static const uint8_t	utag[] = { 0, 0, 0, 0 };
static const uint8_t	ucreator[] = { 0, 0, 0, 0 };/* { 'U', 'N', 'I', 'X' };*/
static const uint8_t	utype[] = { 0, 0, 0, 0 };/* { 'T', 'E', 'X', 'T' };*/
static const short	usize = 256;


int afp_geticoninfo(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *rbuflen)
{
    struct vol	*vol;
    unsigned char	fcreator[ 4 ], ih[ 12 ];
    uint16_t	vid, iindex, bsize;

    *rbuflen = 0;
    ibuf += 2;

    memcpy( &vid, ibuf, sizeof( vid ));
    ibuf += sizeof( vid );
    if (NULL == ( vol = getvolbyvid( vid )) ) {
        return AFPERR_PARAM;
    }

    memcpy( fcreator, ibuf, sizeof( fcreator ));
    ibuf += sizeof( fcreator );
    memcpy( &iindex, ibuf, sizeof( iindex ));
    iindex = ntohs( iindex );

    if ( memcmp( fcreator, ucreator, sizeof( ucreator )) == 0 ) {
        if ( iindex > 1 ) {
            return AFPERR_NOITEM;
        }
        memcpy( ih, utag, sizeof( utag ));
        memcpy( ih + sizeof( utag ), utype, sizeof( utype ));
        *( ih + sizeof( utag ) + sizeof( utype )) = 1;
        *( ih + sizeof( utag ) + sizeof( utype ) + 1 ) = 0;
        memcpy( ih + sizeof( utag ) + sizeof( utype ) + 2, &usize,
                sizeof( usize ));
        memcpy( rbuf, ih, sizeof( ih ));
        *rbuflen = sizeof( ih );
        return AFP_OK;
    }

    if ( iconopen( vol, fcreator, O_RDONLY, 0 ) < 0) {
        return AFPERR_NOITEM;
    }

    if ( iindex < si.sdt_index ) {
        if ( lseek( si.sdt_fd, (off_t) 0L, SEEK_SET ) < 0 ) {
            return AFPERR_PARAM;
        }
        si.sdt_index = 1;
    }

    /*
     * Position to the correct spot.
     */
    for (;;) {
        if ( read( si.sdt_fd, ih, sizeof( ih )) != sizeof( ih )) {
            close( si.sdt_fd );
            si.sdt_fd = -1;
            return AFPERR_NOITEM;
        }
        memcpy( &bsize, ih + 10, sizeof( bsize ));
        bsize = ntohs(bsize);
        if ( lseek( si.sdt_fd, (off_t) bsize, SEEK_CUR ) < 0 ) {
            LOG(log_error, logtype_afpd, "afp_iconinfo(%s): lseek: %s", icon_dtfile(vol, fcreator), strerror(errno) );
            return AFPERR_PARAM;
        }
        if ( si.sdt_index == iindex ) {
            memcpy( rbuf, ih, sizeof( ih ));
            *rbuflen = sizeof( ih );
            return AFP_OK;
        }
        si.sdt_index++;
    }
}


int afp_geticon(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *rbuflen)
{
    struct vol	*vol;
    off_t       offset;
    ssize_t	rc, buflen;
    uint8_t	fcreator[ 4 ], ftype[ 4 ], itype, ih[ 12 ];
    uint16_t	vid, bsize, rsize;

    buflen = *rbuflen;
    *rbuflen = 0;
    ibuf += 2;

    memcpy( &vid, ibuf, sizeof( vid ));
    ibuf += sizeof( vid );
    if (NULL == ( vol = getvolbyvid( vid )) ) {
        return AFPERR_PARAM;
    }

    memcpy( fcreator, ibuf, sizeof( fcreator ));
    ibuf += sizeof( fcreator );
    memcpy( ftype, ibuf, sizeof( ftype ));
    ibuf += sizeof( ftype );
    itype = (unsigned char) *ibuf++;
    ibuf++;
    memcpy( &bsize, ibuf, sizeof( bsize ));
    bsize = ntohs( bsize );


    if ( iconopen( vol, fcreator, O_RDONLY, 0 ) < 0) {
        return AFPERR_NOITEM;
    }

    if ( lseek( si.sdt_fd, (off_t) 0L, SEEK_SET ) < 0 ) {
        close(si.sdt_fd);
        si.sdt_fd = -1;
        LOG(log_error, logtype_afpd, "afp_geticon(%s): lseek: %s", icon_dtfile(vol, fcreator), strerror(errno));
        return AFPERR_PARAM;
    }

    si.sdt_index = 1;
    offset = 0;
    while (( rc = read( si.sdt_fd, ih, sizeof( ih ))) > 0 ) {
        si.sdt_index++;
        offset += sizeof(ih);
        if ( memcmp( ih + sizeof( int ), ftype, sizeof( ftype )) == 0 &&
                *(ih + sizeof( int ) + sizeof( ftype )) == itype ) {
            break;
        }
        memcpy( &rsize, ih + 10, sizeof( rsize ));
        rsize = ntohs( rsize );
        if ( lseek( si.sdt_fd, (off_t) rsize, SEEK_CUR ) < 0 ) {
            LOG(log_error, logtype_afpd, "afp_geticon(%s): lseek: %s", icon_dtfile(vol, fcreator), strerror(errno) );
            return AFPERR_PARAM;
        }
        offset += rsize;
    }

    if ( rc < 0 ) {
        LOG(log_error, logtype_afpd, "afp_geticon(%s): read: %s", icon_dtfile(vol, fcreator), strerror(errno));
        return AFPERR_PARAM;
    }

    if ( rc == 0 ) {
        return AFPERR_NOITEM;
    }

    memcpy( &rsize, ih + 10, sizeof( rsize ));
    rsize = ntohs( rsize );
#define min(a,b)	((a)<(b)?(a):(b))
    rc = min( bsize, rsize );

    if ((obj->proto == AFPPROTO_DSI) && buflen < rc) {
        DSI *dsi = obj->dsi;
        struct stat st;
        off_t size;

        size = (fstat(si.sdt_fd, &st) < 0) ? 0 : st.st_size;
        if (size < rc + offset) {
            return AFPERR_PARAM;
        }

#ifndef WITH_SENDFILE
        if ((buflen = dsi_readinit(dsi, rbuf, buflen, rc, AFP_OK)) < 0)
            goto geticon_exit;
#endif

        *rbuflen = buflen;
        /* do to the streaming nature, we have to exit if we encounter
         * a problem. much confusion results otherwise. */
        while (*rbuflen > 0) {
#ifdef WITH_SENDFILE
            if (dsi_stream_read_file(dsi, si.sdt_fd, offset, dsi->datasize, AFP_OK) < 0) {
                switch (errno) {
                case ENOSYS:
                case EINVAL:  /* there's no guarantee that all fs support sendfile */
                    break;
                default:
                    goto geticon_exit;
                }
            }
            else {
                dsi_readdone(dsi);
                return AFP_OK;
            }
#endif
            buflen = read(si.sdt_fd, rbuf, *rbuflen);
            if (buflen < 0)
                goto geticon_exit;

            /* dsi_read() also returns buffer size of next allocation */
            buflen = dsi_read(dsi, rbuf, buflen); /* send it off */
            if (buflen < 0)
                goto geticon_exit;

            *rbuflen = buflen;
        }

        dsi_readdone(dsi);
        return AFP_OK;

geticon_exit:
        LOG(log_error, logtype_afpd, "afp_geticon(%s): %s", icon_dtfile(vol, fcreator), strerror(errno));
        dsi_readdone(dsi);
        obj->exit(EXITERR_SYS);
        return AFP_OK;

    } else {
        if ( read( si.sdt_fd, rbuf, rc ) < rc ) {
            return AFPERR_PARAM;
        }
        *rbuflen = rc;
    }
    return AFP_OK;
}

/* ---------------------- */
static const char		hexdig[] = "0123456789abcdef";
char *dtfile(const struct vol *vol, uint8_t creator[], char *ext )
{
    static char	path[ MAXPATHLEN + 1];
    char	*p;
    unsigned int i;

    strcpy( path, vol->v_dbpath );
    strcat( path, "/" APPLEDESKTOP "/" );
    for ( p = path; *p != '\0'; p++ )
        ;

    if ( !isprint( creator[ 0 ] ) || creator[ 0 ] == '/' ) {
        *p++ = hexdig[ ( creator[ 0 ] & 0xf0 ) >> 4 ];
        *p++ = hexdig[ creator[ 0 ] & 0x0f ];
    } else {
        *p++ = creator[ 0 ];
    }

    *p++ = '/';

    for ( i = 0; i < sizeof( CreatorType ); i++ ) {
        if ( !isprint( creator[ i ] ) || creator[ i ] == '/' ) {
            *p++ = hexdig[ ( creator[ i ] & 0xf0 ) >> 4 ];
            *p++ = hexdig[ creator[ i ] & 0x0f ];
        } else {
            *p++ = creator[ i ];
        }
    }
    *p = '\0';
    strcat( path, ext );

    return path;
}

/* ---------------------------
 * mpath is only a filename
 * did filename parent directory ID.
*/

char *mtoupath(const struct vol *vol, char *mpath, cnid_t did, int utf8)
{
    static char  upath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
    char	*m, *u;
    size_t       inplen;
    size_t       outlen;
    uint16_t	 flags;

    if ( *mpath == '\0' ) {
        strcpy(upath, ".");
        return upath;
    }

    /* set conversion flags */
    flags = vol->v_mtou_flags;

    m = demangle(vol, mpath, did);
    if (m != mpath) {
        return m;
    }

    m = mpath;
    u = upath;

    inplen = strlen(m);
    outlen = MAXPATHLEN;

    if ((size_t)-1 == (outlen = convert_charset ( utf8?CH_UTF8_MAC:vol->v_maccharset, vol->v_volcharset, vol->v_maccharset, m, inplen, u, outlen, &flags)) ) {
        LOG(log_error, logtype_afpd, "conversion from %s to %s for %s failed.", (utf8)?"UTF8-MAC":vol->v_maccodepage, vol->v_volcodepage, mpath);
	    return NULL;
    }

    LOG(log_debug9, logtype_afpd, "mtoupath: '%s':'%s'", mpath, upath);
    return upath;
}

/* ---------------
 * id filename ID
*/
char *utompath(const struct vol *vol, char *upath, cnid_t id, int utf8)
{
    static char  mpath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
    char        *m, *u;
    uint16_t    flags;
    size_t       outlen;

    m = mpath;
    outlen = strlen(upath);

    flags = vol->v_utom_flags;

    u = upath;

    /* convert charsets */
    if ((size_t)-1 == ( outlen = convert_charset ( vol->v_volcharset, utf8?CH_UTF8_MAC:vol->v_maccharset, vol->v_maccharset, u, outlen, mpath, MAXPATHLEN, &flags)) ) {
        LOG(log_error, logtype_afpd, "Conversion from %s to %s for %s (%u) failed.", vol->v_volcodepage, vol->v_maccodepage, u, ntohl(id));
	goto utompath_error;
    }

    flags = !!(flags & CONV_REQMANGLE);

    if (utf8)
        flags |= 2;

    m = mangle(vol, mpath, outlen, upath, id, flags);

    LOG(log_debug9, logtype_afpd, "utompath: '%s':'%s':'%2.2X'", upath, m, ntohl(id));
    return m;

utompath_error:
    u = "???";
    m = mangle(vol, u, strlen(u), upath, id, utf8?3:1);
    return m;
}

/* ------------------------- */
static int ad_addcomment(const AFPObj *obj, struct vol *vol, struct path *path, char *ibuf)
{
    struct ofork        *of;
    char                *name, *upath;
    int                 isadir;
    int			clen;
    struct adouble	ad, *adp;

    clen = (uint8_t)*ibuf++;
    clen = min( clen, 199 );

    upath = path->u_name;
    if (check_access(obj, vol, upath, OPENACC_WR ) < 0) {
        return AFPERR_ACCESS;
    }

    isadir = path_isadir(path);
    if (isadir || !(of = of_findname(vol, path))) {
        ad_init(&ad, vol);
        adp = &ad;
    } else
        adp = of->of_ad;

    if (ad_open(adp, upath,
                ADFLAGS_HF | ( isadir ? ADFLAGS_DIR : 0) | ADFLAGS_CREATE | ADFLAGS_RDWR,
                0666) < 0 ) {
        return AFPERR_ACCESS;
    }

    if (ad_getentryoff(adp, ADEID_COMMENT) && ad_entry(adp, ADEID_COMMENT)) {
        if ( ad_get_MD_flags( adp ) & O_CREAT ) {
            if ( *path->m_name == '\0' ) {
                name = (char *)curdir->d_m_name->data;
            } else {
                name = path->m_name;
            }
            ad_setname(adp, name);
        }
        ad_setentrylen( adp, ADEID_COMMENT, clen );
        memcpy( ad_entry( adp, ADEID_COMMENT ), ibuf, clen );
        ad_flush( adp );
    }
    ad_close(adp, ADFLAGS_HF);
    return AFP_OK;
}

/* ----------------------------- */
int afp_addcomment(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, size_t *rbuflen)
{
    struct vol		*vol;
    struct dir		*dir;
    struct path         *path;
    uint32_t           did;
    uint16_t		vid;

    *rbuflen = 0;
    ibuf += 2;

    memcpy( &vid, ibuf, sizeof( vid ));
    ibuf += sizeof( vid );
    if (NULL == ( vol = getvolbyvid( vid )) ) {
        return AFPERR_PARAM;
    }

    memcpy( &did, ibuf, sizeof( did ));
    ibuf += sizeof( did );
    if (NULL == ( dir = dirlookup( vol, did )) ) {
	return afp_errno;
    }

    if (NULL == ( path = cname( vol, dir, &ibuf )) ) {
	return get_afp_errno(AFPERR_NOOBJ);
    }

    if ((intptr_t)ibuf & 1 ) {
        ibuf++;
    }

    return ad_addcomment(obj, vol, path, ibuf);
}

/* -------------------- */
static int ad_getcomment(struct vol *vol, struct path *path, char *rbuf, size_t *rbuflen)
{
    struct adouble	ad, *adp;
    struct ofork        *of;
    char		*upath;
    int                 isadir;
    int			clen;

    upath = path->u_name;
    isadir = path_isadir(path);
    if (isadir || !(of = of_findname(vol, path))) {
        ad_init(&ad, vol);
        adp = &ad;
    } else
        adp = of->of_ad;

    if ( ad_metadata( upath, (isadir ? ADFLAGS_DIR : 0), adp) < 0 ) {
        return AFPERR_NOITEM;
    }

    if (!ad_getentryoff(adp, ADEID_COMMENT) || !ad_entry(adp, ADEID_COMMENT)) {
        ad_close(adp, ADFLAGS_HF);
        return AFPERR_NOITEM;
    }
    /*
     * Make sure the AD file is not bogus.
     */
    if ( ad_getentrylen( adp, ADEID_COMMENT ) <= 0 ||
            ad_getentrylen( adp, ADEID_COMMENT ) > 199 ) {
        ad_close(adp, ADFLAGS_HF);
        return AFPERR_NOITEM;
    }

    clen = min( ad_getentrylen( adp, ADEID_COMMENT ), 128 ); /* OSX only use 128, greater kill Adobe CS2 */
    *rbuf++ = clen;
    memcpy( rbuf, ad_entry( adp, ADEID_COMMENT ), clen);
    *rbuflen = clen + 1;
    ad_close(adp, ADFLAGS_HF);

    return AFP_OK;
}

/* -------------------- */
int afp_getcomment(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *rbuflen)
{
    struct vol		*vol;
    struct dir		*dir;
    struct path         *s_path;
    uint32_t		did;
    uint16_t		vid;

    *rbuflen = 0;
    ibuf += 2;

    memcpy( &vid, ibuf, sizeof( vid ));
    ibuf += sizeof( vid );
    if (NULL == ( vol = getvolbyvid( vid )) ) {
        return AFPERR_PARAM;
    }

    memcpy( &did, ibuf, sizeof( did ));
    ibuf += sizeof( did );
    if (NULL == ( dir = dirlookup( vol, did )) ) {
	return afp_errno;
    }

    if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
	return get_afp_errno(AFPERR_NOOBJ);
    }

    return ad_getcomment(vol, s_path, rbuf, rbuflen);
}

/* ----------------------- */
static int ad_rmvcomment(const AFPObj *obj, struct vol *vol, struct path *path)
{
    struct adouble	ad, *adp;
    struct ofork        *of;
    int                 isadir;
    char		*upath;

    upath = path->u_name;
    if (check_access(obj, vol, upath, OPENACC_WR ) < 0) {
        return AFPERR_ACCESS;
    }

    isadir = path_isadir(path);
    if (isadir || !(of = of_findname(vol, path))) {
        ad_init(&ad, vol);
        adp = &ad;
    } else
        adp = of->of_ad;

    if ( ad_open(adp, upath, ADFLAGS_HF | ADFLAGS_RDWR | (isadir ? ADFLAGS_DIR : 0)) < 0 ) {
        switch ( errno ) {
        case ENOENT :
            return AFPERR_NOITEM;
        case EACCES :
            return AFPERR_ACCESS;
        default :
            return AFPERR_PARAM;
        }
    }

    if (ad_getentryoff(adp, ADEID_COMMENT)) {
        ad_setentrylen( adp, ADEID_COMMENT, 0 );
        ad_flush( adp );
    }
    ad_close(adp, ADFLAGS_HF);
    return AFP_OK;
}

/* ----------------------- */
int afp_rmvcomment(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, size_t *rbuflen)
{
    struct vol		*vol;
    struct dir		*dir;
    struct path         *s_path;
    uint32_t		did;
    uint16_t		vid;

    *rbuflen = 0;
    ibuf += 2;

    memcpy( &vid, ibuf, sizeof( vid ));
    ibuf += sizeof( vid );
    if (NULL == ( vol = getvolbyvid( vid )) ) {
        return AFPERR_PARAM;
    }

    memcpy( &did, ibuf, sizeof( did ));
    ibuf += sizeof( did );
    if (NULL == ( dir = dirlookup( vol, did )) ) {
	return afp_errno;
    }

    if (NULL == ( s_path = cname( vol, dir, &ibuf ))) {
	return get_afp_errno(AFPERR_NOOBJ);
    }

    return ad_rmvcomment(obj, vol, s_path);
}
