/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*-
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 * Editor Settings: expandtabs and use 4 spaces for indentation */

/*
 * Copyright Likewise Software
 * All rights reserved.
 *
 * 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, see
 * <http://www.gnu.org/licenses/>.
 *
 * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
 * TERMS AS WELL.  IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
 * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
 * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
 * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE.  IF YOU
 * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
 * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
 * license@likewisesoftware.com
 */

/*
 * Copyright (C) Likewise Software. All rights reserved.
 *
 * Module Name:
 *
 *        ea_xattr.c
 *
 * Abstract:
 *
 *        Likewise Posix File System Driver (PVFS)
 *
 *        Extended Attributes storage in POSIX extended attributes
 *
 *        WARNING: THIS IS A TEST IMPLEMENTATION.  DO NOT USE IN PRODUCTION
 *        ENVIRONMENTS.  THIS IMPLEMENTATION IS LACKING AT LEAST IN THE
 *        FOLLOWING AREAS:
 *
 *        1. Locking.  The attributes list is not locked when accessed.  Reading
 *        an incomplete or corrupt value is possible.
 *
 *        2. The size of a single extended attribute value is limited to 4096
 *        bytes.
 *
 *        Look at ea.c for more information.
 *
 * Authors: Evgeny Popovich <epopovich@likewise.com>
 */

#include "pvfs.h"

// Only support the reference EA implementation on Linux
#if defined(HAVE_EA_SUPPORT) && defined(__LWI_LINUX__)


#define PVFS_EA_PREFIX          "user.lwea_"


NTSTATUS
PvfsGetFileEaXattr(
    IN PPVFS_CCB pCcb,
    IN PSTR pszEaName,
    OUT PPVFS_EA_VALUE pValue
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSTR pszDiskEaName = NULL;
    ssize_t retVal = 0;
    int sysErr = 0;

    ntStatus = LwRtlCStringAllocatePrintf(&pszDiskEaName, "%s%s",
                                          PVFS_EA_PREFIX, pszEaName);
    BAIL_ON_NT_STATUS(ntStatus);

    retVal = fgetxattr(pCcb->fd, pszDiskEaName, pValue->Value,
                       PVFS_MAX_EA_VALUE_SIZE);
    if (retVal == -1)
    {
        PVFS_BAIL_ON_UNIX_ERROR(sysErr, ntStatus);
    }

    pValue->ValueLength = retVal;

cleanup:

    LwRtlCStringFree(&pszDiskEaName);

    return ntStatus;

error:

    pValue->ValueLength = 0;

    goto cleanup;
}

NTSTATUS
PvfsGetFileEasXattr(
    IN PPVFS_CCB pCcb,
    OUT PULONG pArraySize,
    OUT PSTR** pppszEaNamesArray,
    OUT PPVFS_EA_VALUE* ppValuesArray
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PBYTE pNamesList = NULL;
    ULONG namesListLengthHint = 0;
    ULONG namesListLength = 0;
    ssize_t sysResult = 0;
    int sysErr = 0;
    int allocationRetries = 2;
    int i = 0;
    PSTR pszCurrentName = 0;
    ULONG prefixLength = strlen(PVFS_EA_PREFIX);

    ULONG arraySize = 0;
    PSTR* ppszEaNamesArray = NULL;
    PPVFS_EA_VALUE pValuesArray = NULL;

    // Count relevant extended attributes

    sysResult = flistxattr(pCcb->fd, NULL, 0);
    if (sysResult == -1)
    {
        PVFS_BAIL_ON_UNIX_ERROR(sysErr, ntStatus);
    }
    namesListLengthHint = sysResult;

    if (namesListLengthHint <= 1)
    {
        goto cleanup;
    }

    for (i = 0; i < allocationRetries; ++i)
    {
        ntStatus = PvfsAllocateMemory(
                        OUT_PPVOID(&pNamesList),
                        namesListLengthHint,
                        FALSE);
        BAIL_ON_NT_STATUS(ntStatus);

        sysResult = flistxattr(pCcb->fd, (char*)pNamesList,
                               namesListLengthHint);
        if (sysResult == -1)
        {
            if (errno == ERANGE)
            {
                ntStatus = LwErrnoToNtStatus(errno);
                PvfsFreeMemory(OUT_PPVOID(&pNamesList));
                namesListLengthHint *= 2;
                continue;
            }
            else
            {
                PVFS_BAIL_ON_UNIX_ERROR(sysErr, ntStatus);
            }
        }
        namesListLength = sysResult;

        break;
    }
    BAIL_ON_NT_STATUS(ntStatus);

    pszCurrentName = (PSTR)pNamesList;
    while ((pszCurrentName - (PSTR)pNamesList) < namesListLength)
    {
        ULONG currentNameLength = strlen(pszCurrentName);

        if (currentNameLength <= prefixLength)
        {
            pszCurrentName += currentNameLength + 1;
            continue;
        }

        if (!memcmp(pszCurrentName, PVFS_EA_PREFIX, prefixLength))
        {
            ++arraySize;
        }

        pszCurrentName += currentNameLength + 1;
    }

    ntStatus = PvfsAllocateMemory(
                    OUT_PPVOID(&pValuesArray),
                    sizeof(*pValuesArray) * arraySize,
                    FALSE);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = PvfsAllocateMemory(
                    OUT_PPVOID(&ppszEaNamesArray),
                    sizeof(*ppszEaNamesArray) * arraySize,
                    TRUE);
    BAIL_ON_NT_STATUS(ntStatus);

    pszCurrentName = (PSTR)pNamesList;
    i = 0;
    while ((pszCurrentName - (PSTR)pNamesList) < namesListLength)
    {
        ULONG currentNameLength = strlen(pszCurrentName);

        if (currentNameLength <= prefixLength)
        {
            pszCurrentName += currentNameLength + 1;
            continue;
        }

        if (!memcmp(pszCurrentName, PVFS_EA_PREFIX, prefixLength))
        {
            ntStatus = PvfsGetFileEaXattr(pCcb, pszCurrentName + prefixLength,
                                          &pValuesArray[i]);
            BAIL_ON_NT_STATUS(ntStatus);

            ntStatus = LwRtlCStringDuplicate(&ppszEaNamesArray[i],
                                             pszCurrentName + prefixLength);
            BAIL_ON_NT_STATUS(ntStatus);

            ++i;
            if (i == arraySize)
            {
                break;
            }
        }

        pszCurrentName += currentNameLength + 1;
    }

    if (i != arraySize)
    {
        LWIO_ASSERT(FALSE);
    }

cleanup:

    PvfsFreeMemory(OUT_PPVOID(&pNamesList));

    *pArraySize = arraySize;
    *pppszEaNamesArray = ppszEaNamesArray;
    *ppValuesArray = pValuesArray;

    return ntStatus;

error:

    PvfsFreeMemory(OUT_PPVOID(&pValuesArray));
    if (ppszEaNamesArray)
    {
        for (i = 0; i < arraySize; ++i)
        {
            LwRtlCStringFree(&ppszEaNamesArray[i]);
        }
        PvfsFreeMemory(OUT_PPVOID(&ppszEaNamesArray));
    }
    arraySize = 0;

    goto cleanup;
}

NTSTATUS
PvfsSetFileEaXattr(
    IN PPVFS_CCB pCcb,
    IN PSTR pszEaName,
    IN PPVFS_EA_VALUE pValue
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSTR pszDiskEaName = NULL;
    int sysErr = 0;

    ntStatus = LwRtlCStringAllocatePrintf(&pszDiskEaName, "%s%s",
                                          PVFS_EA_PREFIX, pszEaName);
    BAIL_ON_NT_STATUS(ntStatus);

    if (fsetxattr(pCcb->fd, pszDiskEaName, pValue->Value,
                  pValue->ValueLength, 0) == -1)
    {
        PVFS_BAIL_ON_UNIX_ERROR(sysErr, ntStatus);
    }

cleanup:

    LwRtlCStringFree(&pszDiskEaName);

    return ntStatus;

error:

    goto cleanup;
}

NTSTATUS
PvfsRemoveFileEaXattr(
    IN PPVFS_CCB pCcb,
    IN PSTR pszEaName
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSTR pszDiskEaName = NULL;
    int sysErr = 0;

    ntStatus = LwRtlCStringAllocatePrintf(&pszDiskEaName, "%s%s",
                                          PVFS_EA_PREFIX, pszEaName);
    BAIL_ON_NT_STATUS(ntStatus);

    if (fremovexattr(pCcb->fd, pszDiskEaName) == -1)
    {
        PVFS_BAIL_ON_UNIX_ERROR(sysErr, ntStatus);
    }

cleanup:

    LwRtlCStringFree(&pszDiskEaName);

    return ntStatus;

error:

    goto cleanup;
}

#else   /* HAVE_EA_SUPPORT  */

NTSTATUS
PvfsGetFileEaXattr(
    IN PPVFS_CCB pCcb,
    IN PSTR pszEaName,
    OUT PPVFS_EA_VALUE pValue
    )
{
    return STATUS_NOT_SUPPORTED;
}

NTSTATUS
PvfsGetFileEasXattr(
    IN PPVFS_CCB pCcb,
    OUT PULONG pArraySize,
    OUT PSTR** pppszEaNamesArray,
    OUT PPVFS_EA_VALUE* ppValuesArray
    )
{
    return STATUS_NOT_SUPPORTED;
}

NTSTATUS
PvfsSetFileEaXattr(
    IN PPVFS_CCB pCcb,
    IN PSTR pszEaName,
    IN PPVFS_EA_VALUE pValue
    )
{
    return STATUS_NOT_SUPPORTED;
}

NTSTATUS
PvfsRemoveFileEaXattr(
    IN PPVFS_CCB pCcb,
    IN PSTR pszEaName
    )
{
    return STATUS_NOT_SUPPORTED;
}

#endif   /* HAVE_EA_SUPPORT  */
