#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <dispatch/dispatch.h>
#include <xpc/private.h>
#include <xattr_flags.h>
#define FLAG_DELIM_CHAR '#'
#define FLAG_DELIM_STR "#"
struct defaultList {
const char *eaName;
const char *propList;
int flags; };
#define propFlagsPrefix 0x0001 // The name is a prefix, so only look at that part
static const struct defaultList *defaultPropertyTable = NULL;
static const struct defaultList
defaultUnboxedPropertyTable[] = {
{ "com.apple.quarantine", "PCS", 0 }, { "com.apple.TextEncoding", "CS", 0 }, { "com.apple.metadata:", "PS", propFlagsPrefix }, { "com.apple.security.", "S", propFlagsPrefix },
{ XATTR_RESOURCEFORK_NAME, "PCS", 0 }, { XATTR_FINDERINFO_NAME, "PCS", 0 }, { 0, 0, 0 },
};
static const struct defaultList
defaultSandboxedPropertyTable[] = {
{ "com.apple.quarantine", "PCS", 0 }, { "com.apple.TextEncoding", "CS", 0 }, { "com.apple.metadata:", "PS", propFlagsPrefix }, { "com.apple.security.", "N", propFlagsPrefix },
{ XATTR_RESOURCEFORK_NAME, "PCS", 0 }, { XATTR_FINDERINFO_NAME, "PCS", 0 }, { 0, 0, 0 },
};
struct propertyListMapping {
char enable; char disable; xattr_operation_intent_t value;
};
static const struct propertyListMapping
PropertyListMapTable[] = {
{ 'C', 'c', XATTR_FLAG_CONTENT_DEPENDENT },
{ 'P', 'p', XATTR_FLAG_NO_EXPORT },
{ 'N', 'n', XATTR_FLAG_NEVER_PRESERVE },
{ 'S', 's', XATTR_FLAG_SYNCABLE },
{ 0, 0, 0 },
};
static const struct divineIntent {
xattr_operation_intent_t intent;
int (^checker)(xattr_flags_t);
} intentTable[] = {
{ XATTR_OPERATION_INTENT_COPY, ^(xattr_flags_t flags) {
if (flags & XATTR_FLAG_NEVER_PRESERVE)
return 0;
return 1;
} },
{ XATTR_OPERATION_INTENT_SAVE, ^(xattr_flags_t flags) {
if (flags & (XATTR_FLAG_CONTENT_DEPENDENT | XATTR_FLAG_NEVER_PRESERVE))
return 0;
return 1;
} },
{ XATTR_OPERATION_INTENT_SHARE, ^(xattr_flags_t flags) {
if ((flags & (XATTR_FLAG_NO_EXPORT | XATTR_FLAG_NEVER_PRESERVE)) != 0)
return 0;
return 1;
} },
{ XATTR_OPERATION_INTENT_SYNC, ^(xattr_flags_t flags) {
return (flags & (XATTR_FLAG_SYNCABLE | XATTR_FLAG_NEVER_PRESERVE)) == XATTR_FLAG_SYNCABLE;
} },
{ 0, 0 },
};
static const char *
nameInDefaultList(const char *eaname)
{
const struct defaultList *retval;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (_xpc_runtime_is_app_sandboxed()) {
defaultPropertyTable = defaultSandboxedPropertyTable;
} else {
defaultPropertyTable = defaultUnboxedPropertyTable;
}
});
for (retval = defaultPropertyTable; retval->eaName; retval++) {
if ((retval->flags & propFlagsPrefix) != 0 &&
strncmp(retval->eaName, eaname, strlen(retval->eaName)) == 0)
return retval->propList;
if (strcmp(retval->eaName, eaname) == 0)
return retval->propList;
}
return NULL;
}
static const char *
findPropertyList(const char *eaname)
{
const char *ptr = strrchr(eaname, '#');
if (ptr)
return ptr+1;
return NULL;
}
static xattr_operation_intent_t
stringToProperties(const char *proplist)
{
xattr_operation_intent_t retval = 0;
const char *ptr;
for (ptr = proplist; *ptr; ptr++) {
const struct propertyListMapping *mapPtr;
for (mapPtr = PropertyListMapTable; mapPtr->enable; mapPtr++) {
if (*ptr == mapPtr->enable) {
retval |= mapPtr->value;
} else if (*ptr == mapPtr->disable) {
retval &= ~mapPtr->value;
}
}
}
return retval;
}
char *
xattr_name_with_flags(const char *orig, xattr_flags_t propList)
{
char *retval = NULL;
char suffix[66] = { 0 }; char *cur = suffix;
const struct propertyListMapping *mapPtr;
*cur++ = '#';
for (mapPtr = PropertyListMapTable; mapPtr->enable; mapPtr++) {
if ((propList & mapPtr->value) != 0) {
*cur++ = mapPtr->enable;
}
if (cur >= (suffix + sizeof(suffix))) {
errno = ENAMETOOLONG;
return NULL;
}
}
if (cur == suffix + 1) {
retval = strdup(orig);
if (retval == NULL)
errno = ENOMEM;
} else {
const char *defaultEntry = NULL;
if ((defaultEntry = nameInDefaultList(orig)) != NULL &&
strcmp(defaultEntry, suffix + 1) == 0) {
retval = strdup(orig);
} else {
asprintf(&retval, "%s%s", orig, suffix);
}
if (retval == NULL) {
errno = ENOMEM;
} else {
if (strlen(retval) > XATTR_MAXNAMELEN) {
free(retval);
retval = NULL;
errno = ENAMETOOLONG;
}
}
}
return retval;
}
char *
xattr_name_without_flags(const char *eaname)
{
char *retval = NULL;
char *tmp;
if ((tmp = strrchr(eaname, FLAG_DELIM_CHAR)) == NULL) {
retval = strdup(eaname);
} else {
retval = calloc(tmp - eaname + 1, 1);
if (retval) {
strlcpy(retval, eaname, tmp - eaname + 1);
}
}
if (retval == NULL) {
errno = ENOMEM;
}
return retval;
}
int
xattr_intent_with_flags(xattr_operation_intent_t intent, xattr_flags_t flags)
{
const struct divineIntent *ip;
for (ip = intentTable; ip->intent; ip++) {
if (ip->intent == intent) {
return ip->checker(flags);
}
}
if ((flags & XATTR_FLAG_NEVER_PRESERVE) != 0)
return 0;
return 1; }
xattr_flags_t
xattr_flags_from_name(const char *eaname)
{
xattr_flags_t retval = 0;
const char *propList;
propList = findPropertyList(eaname);
if (propList == NULL) {
propList = nameInDefaultList(eaname);
}
if (propList != NULL) {
retval = stringToProperties(propList);
}
return retval;
}
int
xattr_preserve_for_intent(const char *eaname, xattr_operation_intent_t intent)
{
xattr_flags_t flags = xattr_flags_from_name(eaname);
return xattr_intent_with_flags(intent, flags);
}
#include "xattr_properties.h"