/*
 * Extended attribute support for rsync.
 *
 * Copyright (C) 2004 Red Hat, Inc.
 * Copyright (C) 2003-2008 Wayne Davison
 * Written by Jay Fenlason.
 *
 * 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 3 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, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "sysxattrs.h"

#ifdef SUPPORT_XATTRS

#if defined HAVE_LINUX_XATTRS

ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
{
	return lgetxattr(path, name, value, size);
}

ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
{
	return fgetxattr(filedes, name, value, size);
}

int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
{
	return lsetxattr(path, name, value, size, 0);
}

int sys_lremovexattr(const char *path, const char *name)
{
	return lremovexattr(path, name);
}

ssize_t sys_llistxattr(const char *path, char *list, size_t size)
{
	return llistxattr(path, list, size);
}

#elif HAVE_OSX_XATTRS

ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
{
	return getxattr(path, name, value, size, 0, XATTR_NOFOLLOW);
}

ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
{
	return fgetxattr(filedes, name, value, size, 0, 0);
}

int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
{
	return setxattr(path, name, value, size, 0, XATTR_NOFOLLOW);
}

int sys_lremovexattr(const char *path, const char *name)
{
	return removexattr(path, name, XATTR_NOFOLLOW);
}

ssize_t sys_llistxattr(const char *path, char *list, size_t size)
{
	return listxattr(path, list, size, XATTR_NOFOLLOW);
}

#elif HAVE_FREEBSD_XATTRS

ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
{
	return extattr_get_link(path, EXTATTR_NAMESPACE_USER, name, value, size);
}

ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
{
	return extattr_get_fd(filedes, EXTATTR_NAMESPACE_USER, name, value, size);
}

int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
{
	return extattr_set_link(path, EXTATTR_NAMESPACE_USER, name, value, size);
}

int sys_lremovexattr(const char *path, const char *name)
{
	return extattr_delete_link(path, EXTATTR_NAMESPACE_USER, name);
}

ssize_t sys_llistxattr(const char *path, char *list, size_t size)
{
	unsigned char keylen;
	ssize_t off, len = extattr_list_link(path, EXTATTR_NAMESPACE_USER, list, size);

	if (len <= 0 || (size_t)len > size)
		return len;

	/* FreeBSD puts a single-byte length before each string, with no '\0'
	 * terminator.  We need to change this into a series of null-terminted
	 * strings.  Since the size is the same, we can simply transform the
	 * output in place. */
	for (off = 0; off < len; off += keylen + 1) {
		keylen = ((unsigned char*)list)[off];
		if (off + keylen >= len) {
			/* Should be impossible, but kernel bugs happen! */
			errno = EINVAL;
			return -1;
		}
		memmove(list+off, list+off+1, keylen);
		list[off+keylen] = '\0';
	}

	return len;
}

#elif defined(__OS2__)

#define INCL_LONGLONG
#define INCL_DOS
#define INCL_DOSPROCESS
#define INCL_DOSPROFILE
#define INCL_DOSMISC
#define INCL_DOSMODULEMGR
#define INCL_DOSERRORS

#include <os2.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <types.h>
ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
{
	return unigetxattr(path, 0, name, value, size);
}

ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
{
	return unigetxattr(0, filedes, name, value, size);
}

int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
{
	return unisetxattr (path, 0, name, value, size, 0);
}

int sys_lremovexattr(const char *path, const char *name)
{
	return uniremovexattr (path, 0, name);
}

ssize_t sys_llistxattr(const char *path, char *list, size_t size)
{
	return unilistxattr(path, 0, list, size);
}

ssize_t unigetxattr (const char *path, int file, const char *name, void *value, size_t size)
{
	int rc, namelen;
	EAOP2       eaop2 = {0,0,0};
	PGEA2LIST   pgea2list = NULL;
	PFEA2LIST   pfea2list = NULL;
	char * p;

	if ((!path && !file) || !name)
	{
		errno = EINVAL;
		return -1;
	}
	namelen = strlen(name);
	if (namelen > 0xFF)
	{
		errno = EINVAL;
		return -1;
	}
	pgea2list = (PGEA2LIST)calloc(sizeof(GEA2LIST) + namelen + 1, 1);
	pgea2list->list[0].oNextEntryOffset = 0;
	pgea2list->list[0].cbName = namelen;
	strcpy(pgea2list->list[0].szName, name);
	pgea2list->cbList = sizeof(GEA2LIST) + namelen;

	// max ea is 64kb
	pfea2list = (PFEA2LIST)calloc(sizeof(FEA2LIST) + 0x10000, 1);
	pfea2list->cbList = sizeof(FEA2LIST) + 0x10000;

	eaop2.fpGEA2List = pgea2list;
	eaop2.fpFEA2List = pfea2list;
	eaop2.oError = 0;
	do
	{
		if (path)
		{
			char npath[CCHMAXPATH + 1] = {0};
			strncpy(npath, path, CCHMAXPATH);
			for (p = npath; *p; p++)
			{
				if (*p == '/') *p = '\\';	// 29 Jul 08 SHL fixme to be gone? - OS/2 does not care?
			}
			rc = DosQueryPathInfo(npath, FIL_QUERYEASFROMLIST, &eaop2, sizeof(eaop2));
			if (rc)
				rprintf(FERROR, "* unigetxattr: DosQueryPathInfo(%s, FIL_QUERYEASFROMLIST) returned %u %s:%u\n", npath, rc, __FILE__, __LINE__);	// 28 Jul 08 SHL debug
			else if (verbose > 2)
				rprintf(FINFO, "%s has %lu EA bytes\n", path, eaop2.fpFEA2List->cbList);	// 28 Jul 08 SHL
		}
		else
		{
			rc = DosQueryFileInfo( file, FIL_QUERYEASFROMLIST, &eaop2, sizeof(eaop2));
			if (rc)
				rprintf(FERROR, "* unigetxattr: DosQueryFileInfo(%u, FIL_QUERYEASFROMLIST) returned %u %s:%u\n", file, rc, __FILE__, __LINE__);	// 28 Jul 08 SHL debug
			else if (verbose > 2)
				rprintf(FINFO, "Handle %d has %lu EA bytes\n", file, eaop2.fpFEA2List->cbList);	// 28 Jul 08 SHL
		}
		if (rc)
		{
			break;
		}
		if (strnicmp(pfea2list->list[0].szName, name, namelen) || pfea2list->list[0].cbValue == 0)
		{
			errno = ENOATTR;
			rc = -1;
			break;
		}
		rc = pfea2list->list[0].cbValue;
		if (value)
		{
			if ((size_t)rc > size)
			{
				errno = ERANGE;
				rc = -1;
			}
			else
			{
				p = pfea2list->list[0].szName + pfea2list->list[0].cbName + 1;
				memcpy(value, p, rc);
			}
		}
	} while (0);
	if (pgea2list)
	{
		free(pgea2list);
	}
	if (pgea2list)
	{
		free(pfea2list);
	}

	return rc;
}

ssize_t unilistxattr (const char *path, int file, char *list, size_t size)
{
	ssize_t gotsize = 0;
	unsigned long ulCount = -1;
	int rc;
	char * buf, *p = list;
	PFEA2 pfea;
	FILESTATUS4 stat;
	char npath[CCHMAXPATH + 1] = {0};
	memset(&stat, 0, sizeof(stat));
	if (!path && !file)
	{
		errno = EINVAL;
		return -1;
	}
	if (path)
	{
		char * p;
		strncpy(npath, path, CCHMAXPATH);
		for (p = npath; *p; p++)
		{
			if (*p == '/') *p = '\\';
		}
		rc = DosQueryPathInfo(npath, FIL_QUERYEASIZE, &stat, sizeof(stat));
		if (rc)
			rprintf(FERROR, "* unilistxattr: DosQueryPathInfo(%s, FIL_QUERYEASIZE) returned %u %s:%u\n", npath, rc, __FILE__, __LINE__);	// 28 Jul 08 SHL debug
		else if (verbose > 2)
			rprintf(FINFO, "%s has %lu EA bytes\n", path, stat.cbList);	// 28 Jul 08 SHL
	}
	else
	{
		rc = DosQueryFileInfo( file, FIL_QUERYEASIZE, &stat, sizeof(stat));
		if (rc)
			rprintf(FERROR, "* unilistxattr: DosQueryFilInfo(%u, FIL_QUERYEASIZE) returned %u %s:%u\n", file, rc, __FILE__, __LINE__);	// 28 Jul 08 SHL debug
		else if (verbose > 2)
			rprintf(FINFO, "Handle %d has %lu EA bytes\n", file, stat.cbList);	// 28 Jul 08 SHL
	}
	if (rc)
	{
		return -1;
	}
	if (stat.cbList <= 4)
	{
		// NO ea
		return 0;
	}
	//YD DosEnumAttribute doesn't like high-mem buffers, get a low one.
	buf = (char *)_tmalloc(stat.cbList * 2);
	rc = DosEnumAttribute(path ? 1 : 0, path ? (PVOID)path : (PVOID)&file, 1, (PVOID)buf, stat.cbList * 2, &ulCount, 1);
	if (rc)
	{
		rprintf(FERROR, "* unilistxattr: DosEnumAttribute returned %u %s:%u\n", rc, __FILE__, __LINE__);	// 28 Jul 08 SHL debug
		_tfree(buf);
		return -1;
	}
	else if (verbose > 2) {
		if (path)
			rprintf(FINFO, "%s has %lu EA items\n", path, ulCount);	// 28 Jul 08 SHL
		else
			rprintf(FINFO, "Handle %d has %lu EA items\n", file, ulCount);	// 28 Jul 08 SHL
	}
	if (ulCount > 0)
	for (pfea = (PFEA2)buf;;pfea = (PFEA2)((char *)pfea + pfea->oNextEntryOffset))
	{
		if (pfea->cbName > 0)
		{
			gotsize += pfea->cbName + 1;
			// Avoid overflow
			if (p && (size_t)gotsize <= size)
			{
				pfea->szName[pfea->cbName] = 0;
				strcpy(p, pfea->szName);
				p += strlen(p) + 1;
			}
		}
		if (!pfea->oNextEntryOffset)
		{
			break;
		}
	}
	_tfree(buf);
	if ((size_t)gotsize > size)
	{
		errno = ERANGE;
		return list ? -1 : gotsize;
	}
	if (verbose > 2)
		rprintf(FINFO, "Returning %ld EA list bytes\n", (unsigned long)gotsize);	// 28 Jul 08 SHL
	return gotsize;
}

int uniremovexattr (const char *path, int file, const char *name)
{
	int rc, namelen;
	EAOP2 eaop2 = {0,0,0};
	PFEA2LIST pfea2list = NULL;
	char buf[300] = {0};

	if ((!path && !file) || !name)
	{
		errno = EINVAL;
		return -1;
	}

	namelen = strlen(name);
	if (namelen > 0xFF)
	{
		errno = EINVAL;
		return -1;
	}

	pfea2list = (PFEA2LIST)buf;
	pfea2list->list[0].cbName = namelen;
	pfea2list->list[0].cbValue = 0;
	pfea2list->list[0].fEA = 0;
	strcpy(pfea2list->list[0].szName, name);
	pfea2list->cbList = sizeof(FEA2LIST) + namelen;
	eaop2.fpFEA2List = pfea2list;

	if (path)
	{
		char npath[CCHMAXPATH + 1];
		char * p;
		strncpy(npath, path, CCHMAXPATH);
		for (p = npath; *p; p++)
		{
			if (*p == '/') *p = '\\';
		}
		rc = DosSetPathInfo(npath, FIL_QUERYEASIZE, &eaop2, sizeof(eaop2), DSPI_WRTTHRU);
		if (rc)
			rprintf(FERROR, "* uniremovexattr: DosSetPathInfo(%s, FIL_QUERYEASIZE) returned %u %s:%u\n", npath, rc, __FILE__, __LINE__);	// 28 Jul 08 SHL debug
		else if (verbose > 1)
			rprintf(FINFO, "Deleted EA %s from %s\n", name, path);	// 28 Jul 08 SHL
	}
	else
	{
		rc = DosSetFileInfo( file, FIL_QUERYEASIZE, &eaop2, sizeof(eaop2));
		if (rc)
			rprintf(FERROR, "* uniremovexattr: DosFileInfo(%u, FIL_QUERYEASIZE) returned %u %s:%u\n", file, rc, __FILE__, __LINE__);	// 28 Jul 08 SHL debug
		else if (verbose > 1)
			rprintf(FINFO, "Deleted EA %s from handle %d \n", name, file);	// 28 Jul 08 SHL
	}
	if (rc)
	{
		return -1;
	}
	return 0;
}

#ifndef XATTR_CREATE
#define XATTR_CREATE  1
#endif
#ifndef XATTR_REPLACE
#define XATTR_REPLACE 2
#endif

int unisetxattr (const char *path, int file, const char *name, const void *value, size_t size, int flags)
{
	int rc, namelen, totalsize;
	EAOP2       eaop2 = {0,0,0};
	PFEA2LIST   pfea2list = NULL;
	char * p;

	if ((!path && !file) || !name || (!value && size))
	{
		errno = EINVAL;
		return -1;
	}
	namelen = strlen(name);
	if (namelen > 0xFF)
	{
		errno = EINVAL;
		return -1;
	}

	if (flags & (XATTR_CREATE | XATTR_REPLACE))
	{
		ssize_t esize = unigetxattr(path, file, name, 0, 0);
		if (flags & XATTR_CREATE && esize > 0)
		{
			errno = EEXIST;
			return -1;
		}
		if (flags & XATTR_REPLACE && esize < 0)
		{
			errno = ENOATTR;
			return -1;
		}
	}

	totalsize = sizeof(FEA2LIST) + size + namelen + 1;

	pfea2list = (PFEA2LIST)calloc(totalsize, 1);
	pfea2list->cbList = totalsize;
	pfea2list->list[0].oNextEntryOffset = 0;
	pfea2list->list[0].cbName = namelen;
	pfea2list->list[0].cbValue = size;
	strcpy(pfea2list->list[0].szName, name);
	if (value)
	{
		memcpy(pfea2list->list[0].szName + namelen + 1, value, size);
	}
	eaop2.fpFEA2List = pfea2list;

	if (path)
	{
		char npath[CCHMAXPATH + 1] = {0};
		strncpy(npath, path, CCHMAXPATH);
		for (p = npath; *p; p++)
		{
			if (*p == '/') *p = '\\';
		}
		rc = DosSetPathInfo(npath, FIL_QUERYEASIZE, &eaop2, sizeof(eaop2), DSPI_WRTTHRU);
		if (rc)
			rprintf(FERROR, "* unisetxattr: DosSetPathInfo(%s, FIL_QUERYEASIZE) returned %u %s:%u\n", npath, rc, __FILE__, __LINE__);	// 28 Jul 08 SHL debug
		else if (verbose > 1)
			rprintf(FINFO, "Set EA %s for %s\n", name, path);	// 28 Jul 08 SHL
	}
	else
	{
		rc = DosSetFileInfo( file, FIL_QUERYEASIZE, &eaop2, sizeof(eaop2));
		if (rc)
			rprintf(FERROR, "* unisetxattr: DosSetFileInfo(%u, FIL_QUERYEASIZE) returned %u %s:%u\n", file, rc, __FILE__, __LINE__);	// 28 Jul 08 SHL debug
		else if (verbose > 1)
			rprintf(FINFO, "Set EA %s for handle %d\n", name, file);	// 28 Jul 08 SHL
	}
	free(pfea2list);
	if (rc)
	{
		return -1;
	}
	return 0;
}

#else

#error You need to create xattr compatibility functions.

#endif

#endif /* SUPPORT_XATTRS */
