208 lines
5.4 KiB
C
208 lines
5.4 KiB
C
#include <sys/types.h>
|
|
#include <sys/extattr.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#define VALUE_SIZE 96
|
|
|
|
static int
|
|
fail(const char *message)
|
|
{
|
|
fprintf(stderr, "%s\n", message);
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
parse_long(const char *text, long minimum, long maximum, long *valuep)
|
|
{
|
|
char *end;
|
|
long value;
|
|
|
|
errno = 0;
|
|
value = strtol(text, &end, 10);
|
|
if (errno != 0 || text[0] == '\0' || end[0] != '\0' ||
|
|
value < minimum || value > maximum)
|
|
return (-1);
|
|
*valuep = value;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
namespace_id(const char *name)
|
|
{
|
|
if (strcmp(name, "user") == 0)
|
|
return (EXTATTR_NAMESPACE_USER);
|
|
if (strcmp(name, "system") == 0)
|
|
return (EXTATTR_NAMESPACE_SYSTEM);
|
|
return (-1);
|
|
}
|
|
|
|
static void
|
|
expected_value(unsigned int index, uint8_t *value)
|
|
{
|
|
char prefix[16];
|
|
size_t length;
|
|
|
|
(void)snprintf(prefix, sizeof(prefix), "value-%02u-", index);
|
|
length = strlen(prefix);
|
|
for (size_t offset = 0; offset < VALUE_SIZE; offset++)
|
|
value[offset] = prefix[offset % length];
|
|
}
|
|
|
|
static int
|
|
get_hit(const char *path, const char *name, unsigned int index)
|
|
{
|
|
uint8_t actual[VALUE_SIZE + 16], expected[VALUE_SIZE];
|
|
ssize_t length;
|
|
|
|
memset(actual, 0xa5, sizeof(actual));
|
|
expected_value(index, expected);
|
|
errno = 0;
|
|
length = extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, actual,
|
|
sizeof(actual));
|
|
if (length != VALUE_SIZE || errno != 0)
|
|
return (fail("extattr hit returned the wrong length or errno"));
|
|
if (memcmp(actual, expected, sizeof(expected)) != 0)
|
|
return (fail("extattr hit returned the wrong value"));
|
|
for (size_t offset = VALUE_SIZE; offset < sizeof(actual); offset++) {
|
|
if (actual[offset] != 0xa5)
|
|
return (fail("extattr hit wrote beyond the value"));
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
get_errno(const char *path, int attrnamespace, const char *name,
|
|
int expected_errno)
|
|
{
|
|
uint8_t actual[VALUE_SIZE + 16];
|
|
ssize_t length;
|
|
|
|
memset(actual, 0xa5, sizeof(actual));
|
|
errno = 0;
|
|
length = extattr_get_file(path, attrnamespace, name, actual,
|
|
sizeof(actual));
|
|
if (length != -1 || errno != expected_errno)
|
|
return (fail("extattr failure returned the wrong positive errno"));
|
|
for (size_t offset = 0; offset < sizeof(actual); offset++) {
|
|
if (actual[offset] != 0xa5)
|
|
return (fail("failed extattr lookup modified the output"));
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
list_user(const char *path)
|
|
{
|
|
uint8_t *buffer;
|
|
ssize_t length;
|
|
size_t offset;
|
|
unsigned int seen;
|
|
|
|
errno = 0;
|
|
length = extattr_list_file(path, EXTATTR_NAMESPACE_USER, NULL, 0);
|
|
if (length <= 0 || errno != 0)
|
|
return (fail("extattr list sizing failed"));
|
|
buffer = malloc((size_t)length);
|
|
if (buffer == NULL)
|
|
return (fail("extattr list allocation failed"));
|
|
if (extattr_list_file(path, EXTATTR_NAMESPACE_USER, buffer,
|
|
(size_t)length) != length) {
|
|
free(buffer);
|
|
return (fail("extattr list transfer failed"));
|
|
}
|
|
seen = 0;
|
|
for (offset = 0; offset < (size_t)length;) {
|
|
char expected[16];
|
|
uint8_t name_length;
|
|
unsigned int index;
|
|
|
|
name_length = buffer[offset++];
|
|
if (name_length == 0 || name_length > (size_t)length - offset) {
|
|
free(buffer);
|
|
return (fail("extattr list record is malformed"));
|
|
}
|
|
for (index = 0; index < 8; index++) {
|
|
(void)snprintf(expected, sizeof(expected), "attr%02u", index);
|
|
if (strlen(expected) == name_length &&
|
|
memcmp(buffer + offset, expected, name_length) == 0)
|
|
break;
|
|
}
|
|
if (index == 8 || (seen & (1U << index)) != 0) {
|
|
free(buffer);
|
|
return (fail("extattr list returned an unknown or duplicate name"));
|
|
}
|
|
seen |= 1U << index;
|
|
offset += name_length;
|
|
}
|
|
free(buffer);
|
|
if (seen != 0xff)
|
|
return (fail("extattr list omitted a user attribute"));
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
concurrent(const char *path, const char *hit, const char *miss,
|
|
const char *collision, long workers, long loops)
|
|
{
|
|
long worker;
|
|
int status;
|
|
pid_t child;
|
|
|
|
for (worker = 0; worker < workers; worker++) {
|
|
child = fork();
|
|
if (child < 0)
|
|
return (fail("fork failed"));
|
|
if (child == 0) {
|
|
for (long iteration = 0; iteration < loops; iteration++) {
|
|
if (get_hit(path, hit, 0) != 0 ||
|
|
get_errno(path, EXTATTR_NAMESPACE_USER, miss, ENOATTR) != 0 ||
|
|
get_errno(path, EXTATTR_NAMESPACE_USER, collision, ENOATTR) != 0)
|
|
_exit(1);
|
|
}
|
|
_exit(0);
|
|
}
|
|
}
|
|
for (worker = 0; worker < workers; worker++) {
|
|
if (wait(&status) < 0 || !WIFEXITED(status) ||
|
|
WEXITSTATUS(status) != 0)
|
|
return (fail("concurrent extattr worker failed"));
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
long first, second;
|
|
int attrnamespace;
|
|
|
|
if (argc == 5 && strcmp(argv[1], "hit") == 0) {
|
|
if (parse_long(argv[4], 0, 7, &first) != 0)
|
|
return (fail("invalid hit index"));
|
|
return (get_hit(argv[2], argv[3], (unsigned int)first));
|
|
}
|
|
if (argc == 6 && strcmp(argv[1], "errno") == 0) {
|
|
attrnamespace = namespace_id(argv[3]);
|
|
if (attrnamespace < 0 || parse_long(argv[5], 1, 255, &first) != 0)
|
|
return (fail("invalid errno arguments"));
|
|
return (get_errno(argv[2], attrnamespace, argv[4], (int)first));
|
|
}
|
|
if (argc == 3 && strcmp(argv[1], "list") == 0)
|
|
return (list_user(argv[2]));
|
|
if (argc == 8 && strcmp(argv[1], "concurrent") == 0) {
|
|
if (parse_long(argv[6], 1, 64, &first) != 0 ||
|
|
parse_long(argv[7], 1, 1000, &second) != 0)
|
|
return (fail("invalid concurrency arguments"));
|
|
return (concurrent(argv[2], argv[3], argv[4], argv[5], first,
|
|
second));
|
|
}
|
|
return (fail("usage: B19b-xattr-probe MODE PATH ARG..."));
|
|
}
|