/*
  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/afp_util.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 */
    if (NULL == (s_path = cname(vol, dir, &ibuf))) {
        LOG(log_debug, logtype_afpd, "afp_listextattr: cname error: %s",
            AfpErr2name(afp_errno));
        return afp_errno;
    }

    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",
            AfpErr2name(afp_errno));
        return afp_errno;
    }

    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",
            AfpErr2name(afp_errno));
        return afp_errno;
    }

    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",
            AfpErr2name(afp_errno));
        return afp_errno;
    }

    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;
}
