/*
  Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>

  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 <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <atalk/adouble.h>
#include <atalk/afp.h>
#include <atalk/ea.h>
#include <atalk/globals.h>
#include <atalk/logger.h>
#include <atalk/netatalk_conf.h>
#include <atalk/util.h>
#include <atalk/vfs.h>

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

static const char *ea_finderinfo = "com.apple.FinderInfo";
static const char *ea_resourcefork = "com.apple.ResourceFork";


/***************************************
 * AFP funcs
 ****************************************/

/*
  Note: we're being called twice. Firstly the client only want the size of all
  EA names, secondly it wants these names. In order to avoid scanning EAs twice
  we cache them in a static buffer.
*/
int afp_listextattr(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *rbuflen)
{
    int                 ret, oflag = 0, adflags = 0, fd = -1;
    uint16_t            vid;
    uint16_t            bitmap;
    uint32_t            did, maxreply, tmpattr;
    struct vol          *vol;
    struct dir          *dir;
    struct path         *s_path;
    struct stat         *st;
    struct adouble      ad, *adp = NULL;
    struct ofork	*opened = NULL;
    char                *uname, *FinderInfo;
    char                emptyFinderInfo[32] = { 0 };
    size_t              attrbuflen = 0;
    bool                close_ad = false;
    char                attrnamebuf[ATTRNAMEBUFSIZ];

    *rbuflen = 0;
    ibuf += 2;

    /* Get Bitmap and MaxReplySize first */
    memcpy(&bitmap, ibuf +6, sizeof(bitmap));
    bitmap = ntohs(bitmap);

    memcpy(&maxreply, ibuf + 14, sizeof (maxreply));
    maxreply = ntohl(maxreply);

    memcpy(&vid, ibuf, sizeof(vid));
    ibuf += sizeof(vid);
    vol = getvolbyvid(vid);
    if (vol == NULL) {
        LOG(log_debug, logtype_afpd, "afp_listextattr: getvolbyvid error: %s", strerror(errno));
        return AFPERR_ACCESS;
    }

    memcpy(&did, ibuf, sizeof(did));
    ibuf += sizeof(did);
    dir = dirlookup(vol, did);
    if (dir == NULL) {
        LOG(log_debug, logtype_afpd, "afp_listextattr: dirlookup error: %s", strerror(errno));
        return afp_errno;
    }

    if (bitmap & kXAttrNoFollow) {
        oflag = O_NOFOLLOW;
    }

    /* Skip Bitmap, ReqCount, StartIndex and maxreply*/
    ibuf += 12;

    /* get name */
    s_path = cname(vol, dir, &ibuf);
    if (s_path == NULL) {
        LOG(log_debug, logtype_afpd, "afp_listextattr: cname error: %s", strerror(errno));
        return AFPERR_NOOBJ;
    }

    st = &s_path->st;
    if (!s_path->st_valid) {
        /* it's a dir in our cache, we didn't stat it, do it now */
        of_statdir(vol, s_path);
    }
    if (s_path->st_errno != 0) {
        return(AFPERR_NOOBJ);
    }

    uname = s_path->u_name;

    /*
     * We have to check the FinderInfo for the file, because if they
     * aren't all 0 we must return the synthetic attribute
     * "com.apple.FinderInfo".  Note: the client will never (never
     * seen in traces) request that attribute via FPGetExtAttr !
    */

    adp = &ad;
    ad_init(adp, vol);
    ad_init_offsets(adp);

    if (path_isadir(s_path)) {
	    LOG(log_debug, logtype_afpd, "afp_listextattr(%s): is a dir", uname);
        adflags = ADFLAGS_DIR;
	} else {
	    LOG(log_debug, logtype_afpd, "afp_listextattr(%s): is a file", uname);
	    opened = of_findname(vol, s_path);
	    if (opened) {
            adp = opened->of_ad;
            fd = ad_meta_fileno(adp);
	    }
	}

    if (ad_metadata(uname, adflags, adp) != 0 ) {
        switch (errno) {
        case ENOENT:
            break;
        case EACCES:
            LOG(log_error, logtype_afpd, "afp_listextattr(%s): %s: check resource fork permission?",
                uname, strerror(errno));
            return AFPERR_ACCESS;
        default:
            LOG(log_error, logtype_afpd, "afp_listextattr(%s): error getting metadata: %s", uname, strerror(errno));
            return AFPERR_MISC;
        }
    } else {
        close_ad = true;
        FinderInfo = ad_entry(adp, ADEID_FINDERI);
        /* Check if FinderInfo equals default and empty FinderInfo*/
        if (FinderInfo && memcmp(FinderInfo, emptyFinderInfo, 32) != 0) {
            /* FinderInfo contains some non 0 bytes -> include "com.apple.FinderInfo" */
            strcpy(attrnamebuf, ea_finderinfo);
            attrbuflen += strlen(ea_finderinfo) + 1;
            LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): sending com.apple.FinderInfo", uname);
        }

        /* Now check for Resource fork and add virtual EA "com.apple.ResourceFork" if size > 0 */
        LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): Resource fork size: %llu", uname, adp->ad_rlen);

        if (adp->ad_rlen > 0) {
            LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): sending com.apple.ResourceFork.", uname);
            strcpy(attrnamebuf + attrbuflen, ea_resourcefork);
            attrbuflen += strlen(ea_resourcefork) + 1;
        }
    }

    ret = vol->vfs->vfs_ea_list(vol, attrnamebuf, &attrbuflen, uname, oflag, fd);
    if (ret != AFP_OK) {
        attrbuflen = 0;

        switch (ret) {
        case AFPERR_BADTYPE:
            /* its a symlink and client requested O_NOFOLLOW */
            LOG(log_debug, logtype_afpd, "afp_listextattr(%s): encountered symlink with kXAttrNoFollow", uname);
            ret = AFP_OK;
            break;

        default:
            break;
        }
    }

exit:
    bitmap = htons(bitmap);
    memcpy( rbuf, &bitmap, sizeof(bitmap));
    rbuf += sizeof(bitmap);
    *rbuflen += sizeof(bitmap);

    tmpattr = htonl(attrbuflen);
    memcpy( rbuf, &tmpattr, sizeof(tmpattr));
    rbuf += sizeof(tmpattr);
    *rbuflen += sizeof(tmpattr);

    if (maxreply > 0) {
        memcpy( rbuf, attrnamebuf, attrbuflen);
        *rbuflen += attrbuflen;
    }

    if (close_ad) {
        ad_close(adp, ADFLAGS_HF);
    }

    return ret;
}

static char *to_stringz(char *ibuf, uint16_t len)
{
static char attrmname[256];

    if (len > 255)
        /* don't fool with us */
        len = 255;

    /* we must copy the name as its not 0-terminated and I DONT WANT TO WRITE to ibuf */
    strlcpy(attrmname, ibuf, len + 1);
    return attrmname;
}

int afp_getextattr(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *rbuflen)
{
    int                 ret, oflag = 0, fd = -1;
    uint16_t            vid, bitmap, attrnamelen;
    uint32_t            did, maxreply;
    char                attruname[256];
    struct vol          *vol;
    struct dir          *dir;
    struct path         *s_path;
    struct adouble	ad _U_, *adp = NULL;
    struct ofork	*opened = NULL;


    *rbuflen = 0;
    ibuf += 2;

    memcpy( &vid, ibuf, sizeof(vid));
    ibuf += sizeof(vid);
    if (NULL == ( vol = getvolbyvid( vid )) ) {
        LOG(log_debug, logtype_afpd, "afp_getextattr: getvolbyvid error: %s", strerror(errno));
        return AFPERR_ACCESS;
    }

    memcpy( &did, ibuf, sizeof(did));
    ibuf += sizeof(did);
    if (NULL == ( dir = dirlookup( vol, did )) ) {
        LOG(log_debug, logtype_afpd, "afp_getextattr: dirlookup error: %s", strerror(errno));
        return afp_errno;
    }

    memcpy( &bitmap, ibuf, sizeof(bitmap));
    bitmap = ntohs( bitmap );
    ibuf += sizeof(bitmap);

    if (bitmap & kXAttrNoFollow)
        oflag = O_NOFOLLOW;

    /* Skip Offset and ReqCount */
    ibuf += 16;

    /* Get MaxReply */
    memcpy(&maxreply, ibuf, sizeof(maxreply));
    maxreply = ntohl(maxreply);
    ibuf += sizeof(maxreply);

    /* get name */
    if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
        LOG(log_debug, logtype_afpd, "afp_getextattr: cname error: %s", strerror(errno));
        return AFPERR_NOOBJ;
    }

    if ((unsigned long)ibuf & 1)
        ibuf++;

    /* get length of EA name */
    memcpy(&attrnamelen, ibuf, sizeof(attrnamelen));
    attrnamelen = ntohs(attrnamelen);
    ibuf += sizeof(attrnamelen);

    LOG(log_debug, logtype_afpd, "afp_getextattr(%s): EA: %s", s_path->u_name, to_stringz(ibuf, attrnamelen));

    /* Convert EA name in utf8 to unix charset */
    if ( 0 >= convert_string(CH_UTF8_MAC, obj->options.unixcharset, ibuf, attrnamelen, attruname, 256) )
        return AFPERR_MISC;

    /* write bitmap now */
    bitmap = htons(bitmap);
    memcpy(rbuf, &bitmap, sizeof(bitmap));
    rbuf += sizeof(bitmap);
    *rbuflen += sizeof(bitmap);

    if (path_isadir(s_path)) {
	LOG(log_debug, logtype_afpd, "afp_getextattr(%s): is a dir", s_path->u_name);
    } else {
	LOG(log_debug, logtype_afpd, "afp_getextattr(%s): is a file", s_path->u_name);
	opened = of_findname(vol, s_path);
	if (opened) {
	    adp = opened->of_ad;
	    fd = ad_meta_fileno(adp);
	}
    }

    /*
      Switch on maxreply:
      if its 0 we must return the size of the requested attribute,
      if its non 0 we must return the attribute.
    */
    if (maxreply == 0)
        ret = vol->vfs->vfs_ea_getsize(vol, rbuf, rbuflen, s_path->u_name, oflag, attruname, fd);
    else
        ret = vol->vfs->vfs_ea_getcontent(vol, rbuf, rbuflen, s_path->u_name, oflag, attruname, maxreply, fd);

    return ret;
}

int afp_setextattr(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, size_t *rbuflen)
{
    int                 oflag = 0, ret, fd = -1;
    uint16_t            vid, bitmap, attrnamelen;
    uint32_t            did, attrsize;
    char                attruname[256];
    char		*attrmname;
    struct vol          *vol;
    struct dir          *dir;
    struct path         *s_path;
    struct adouble	ad _U_, *adp = NULL;
    struct ofork	*opened = NULL;

    *rbuflen = 0;
    ibuf += 2;

    memcpy( &vid, ibuf, sizeof(vid));
    ibuf += sizeof(vid);
    if (NULL == ( vol = getvolbyvid( vid )) ) {
        LOG(log_debug, logtype_afpd, "afp_setextattr: getvolbyvid error: %s", strerror(errno));
        return AFPERR_ACCESS;
    }

    memcpy( &did, ibuf, sizeof(did));
    ibuf += sizeof(did);
    if (NULL == ( dir = dirlookup( vol, did )) ) {
        LOG(log_debug, logtype_afpd, "afp_setextattr: dirlookup error: %s", strerror(errno));
        return afp_errno;
    }

    memcpy( &bitmap, ibuf, sizeof(bitmap));
    bitmap = ntohs( bitmap );
    ibuf += sizeof(bitmap);

    if (bitmap & kXAttrNoFollow)
        oflag |= O_NOFOLLOW;

    if (bitmap & kXAttrCreate)
        oflag |= O_CREAT;
    else if (bitmap & kXAttrReplace)
        oflag |= O_TRUNC;

    /* Skip Offset */
    ibuf += 8;

    /* get name */
    if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
        LOG(log_debug, logtype_afpd, "afp_setextattr: cname error: %s", strerror(errno));
        return AFPERR_NOOBJ;
    }

    if (path_isadir(s_path)) {
	LOG(log_debug, logtype_afpd, "afp_setextattr(%s): is a dir", s_path->u_name);
    } else {
	LOG(log_debug, logtype_afpd, "afp_setextattr(%s): is a file", s_path->u_name);
	opened = of_findname(vol, s_path);
	if (opened) {
	    adp = opened->of_ad;
	    fd = ad_meta_fileno(adp);
	}
    }


    if ((unsigned long)ibuf & 1)
        ibuf++;

    /* get length of EA name */
    memcpy(&attrnamelen, ibuf, sizeof(attrnamelen));
    attrnamelen = ntohs(attrnamelen);
    ibuf += sizeof(attrnamelen);
    if (attrnamelen > 255)
        return AFPERR_PARAM;

    attrmname = ibuf;
    /* Convert EA name in utf8 to unix charset */
    if ( 0 >= convert_string(CH_UTF8_MAC, obj->options.unixcharset, attrmname, attrnamelen, attruname, 256))
        return AFPERR_MISC;

    ibuf += attrnamelen;
    /* get EA size */
    memcpy(&attrsize, ibuf, sizeof(attrsize));
    attrsize = ntohl(attrsize);
    ibuf += sizeof(attrsize);
    if (attrsize > MAX_EA_SIZE)
        /* we arbitrarily make this fatal */
        return AFPERR_PARAM;

    LOG(log_debug, logtype_afpd, "afp_setextattr(%s): EA: %s, size: %u", s_path->u_name, to_stringz(attrmname, attrnamelen), attrsize);

    ret = vol->vfs->vfs_ea_set(vol, s_path->u_name, attruname, ibuf, attrsize, oflag, fd);

    return ret;
}

int afp_remextattr(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, size_t *rbuflen)
{
    int                 oflag = 0, ret, fd = -1;
    uint16_t            vid, bitmap, attrnamelen;
    uint32_t            did;
    char                attruname[256];
    struct vol          *vol;
    struct dir          *dir;
    struct path         *s_path;
    struct adouble	ad _U_, *adp = NULL;
    struct ofork	*opened = NULL;

    *rbuflen = 0;
    ibuf += 2;

    memcpy( &vid, ibuf, sizeof(vid));
    ibuf += sizeof(vid);
    if (NULL == ( vol = getvolbyvid( vid )) ) {
        LOG(log_debug, logtype_afpd, "afp_remextattr: getvolbyvid error: %s", strerror(errno));
        return AFPERR_ACCESS;
    }

    memcpy( &did, ibuf, sizeof(did));
    ibuf += sizeof(did);
    if (NULL == ( dir = dirlookup( vol, did )) ) {
        LOG(log_debug, logtype_afpd, "afp_remextattr: dirlookup error: %s", strerror(errno));
        return afp_errno;
    }

    memcpy( &bitmap, ibuf, sizeof(bitmap));
    bitmap = ntohs( bitmap );
    ibuf += sizeof(bitmap);

    if (bitmap & kXAttrNoFollow)
        oflag |= O_NOFOLLOW;

    /* get name */
    if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
        LOG(log_debug, logtype_afpd, "afp_remextattr: cname error: %s", strerror(errno));
        return AFPERR_NOOBJ;
    }

    if (path_isadir(s_path)) {
	LOG(log_debug, logtype_afpd, "afp_remextattr(%s): is a dir", s_path->u_name);
    } else {
	LOG(log_debug, logtype_afpd, "afp_remextattr(%s): is a file", s_path->u_name);
	opened = of_findname(vol, s_path);
	if (opened) {
	    adp = opened->of_ad;
	    fd = ad_meta_fileno(adp);
	}
    }

    if ((unsigned long)ibuf & 1)
        ibuf++;

    /* get length of EA name */
    memcpy(&attrnamelen, ibuf, sizeof(attrnamelen));
    attrnamelen = ntohs(attrnamelen);
    ibuf += sizeof(attrnamelen);
    if (attrnamelen > 255)
        return AFPERR_PARAM;

    /* Convert EA name in utf8 to unix charset */
    if ( 0 >= (convert_string(CH_UTF8_MAC, obj->options.unixcharset,ibuf, attrnamelen, attruname, 256)) )
        return AFPERR_MISC;

    LOG(log_debug, logtype_afpd, "afp_remextattr(%s): EA: %s", s_path->u_name, to_stringz(ibuf, attrnamelen));

    ret = vol->vfs->vfs_ea_remove(vol, s_path->u_name, attruname, oflag, fd);

    return ret;
}
