mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 16:35:02 +01:00

This patch removes the final dependency on callers needing access to the features string so aa_features_get_string() can go away. Signed-off-by: Tyler Hicks <tyhicks@canonical.com> Acked-by: John Johansen <john.johansen@canonical.com>
767 lines
19 KiB
C
767 lines
19 KiB
C
/*
|
|
* Copyright (c) 2014
|
|
* Canonical, Ltd. (All rights reserved)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of version 2 of the GNU General Public
|
|
* License published by the Free Software Foundation.
|
|
*
|
|
* 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, contact Novell, Inc. or Canonical
|
|
* Ltd.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <libintl.h>
|
|
#include <locale.h>
|
|
#define _(s) gettext(s)
|
|
|
|
#include "features.h"
|
|
#include "lib.h"
|
|
#include "parser.h"
|
|
|
|
#define FEATURES_FILE "/sys/kernel/security/" MODULE_NAME "/features"
|
|
|
|
#define STRING_SIZE 8192
|
|
|
|
struct aa_features {
|
|
unsigned int ref_count;
|
|
char string[STRING_SIZE];
|
|
};
|
|
|
|
struct features_struct {
|
|
char *buffer;
|
|
int size;
|
|
char *pos;
|
|
};
|
|
|
|
struct component {
|
|
const char *str;
|
|
size_t len;
|
|
};
|
|
|
|
static int features_snprintf(struct features_struct *fst, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
int i, remaining = fst->size - (fst->pos - fst->buffer);
|
|
|
|
if (remaining < 0) {
|
|
errno = EINVAL;
|
|
PERROR(_("Invalid features buffer offset\n"));
|
|
return -1;
|
|
}
|
|
|
|
va_start(args, fmt);
|
|
i = vsnprintf(fst->pos, remaining, fmt, args);
|
|
va_end(args);
|
|
|
|
if (i < 0) {
|
|
errno = EIO;
|
|
PERROR(_("Failed to write to features buffer\n"));
|
|
return -1;
|
|
} else if (i >= remaining) {
|
|
errno = ENOBUFS;
|
|
PERROR(_("Feature buffer full."));
|
|
return -1;
|
|
}
|
|
|
|
fst->pos += i;
|
|
return 0;
|
|
}
|
|
|
|
static int features_dir_cb(DIR *dir, const char *name, struct stat *st,
|
|
void *data)
|
|
{
|
|
struct features_struct *fst = (struct features_struct *) data;
|
|
|
|
/* skip dot files and files with no name */
|
|
if (*name == '.' || !strlen(name))
|
|
return 0;
|
|
|
|
if (features_snprintf(fst, "%s {", name) == -1)
|
|
return -1;
|
|
|
|
if (S_ISREG(st->st_mode)) {
|
|
autoclose int file = -1;
|
|
int len;
|
|
int remaining = fst->size - (fst->pos - fst->buffer);
|
|
|
|
file = openat(dirfd(dir), name, O_RDONLY);
|
|
if (file == -1) {
|
|
PDEBUG("Could not open '%s'", name);
|
|
return -1;
|
|
}
|
|
PDEBUG("Opened features \"%s\"\n", name);
|
|
if (st->st_size > remaining) {
|
|
PDEBUG("Feature buffer full.");
|
|
errno = ENOBUFS;
|
|
return -1;
|
|
}
|
|
|
|
do {
|
|
len = read(file, fst->pos, remaining);
|
|
if (len > 0) {
|
|
remaining -= len;
|
|
fst->pos += len;
|
|
*fst->pos = 0;
|
|
}
|
|
} while (len > 0);
|
|
if (len < 0) {
|
|
PDEBUG("Error reading feature file '%s'\n", name);
|
|
return -1;
|
|
}
|
|
} else if (S_ISDIR(st->st_mode)) {
|
|
if (dirat_for_each(dir, name, fst, features_dir_cb))
|
|
return -1;
|
|
}
|
|
|
|
if (features_snprintf(fst, "}\n") == -1)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_features_dir(const char *filename, char *buffer, int size,
|
|
char *pos)
|
|
{
|
|
struct features_struct fst = { buffer, size, pos };
|
|
|
|
if (dirat_for_each(NULL, filename, &fst, features_dir_cb)) {
|
|
PDEBUG("Failed evaluating %s\n", filename);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int load_features_file(const char *name, char *buffer, size_t size)
|
|
{
|
|
autofclose FILE *f = NULL;
|
|
size_t end;
|
|
|
|
f = fopen(name, "r");
|
|
if (!f)
|
|
return -1;
|
|
|
|
errno = 0;
|
|
end = fread(buffer, 1, size - 1, f);
|
|
if (ferror(f)) {
|
|
if (!errno)
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
buffer[end] = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool islbrace(int c)
|
|
{
|
|
return c == '{';
|
|
}
|
|
|
|
static bool isrbrace(int c)
|
|
{
|
|
return c == '}';
|
|
}
|
|
|
|
static bool isnul(int c)
|
|
{
|
|
return c == '\0';
|
|
}
|
|
|
|
static bool isbrace(int c)
|
|
{
|
|
return islbrace(c) || isrbrace(c);
|
|
}
|
|
|
|
static bool isbrace_or_nul(int c)
|
|
{
|
|
return isbrace(c) || isnul(c);
|
|
}
|
|
|
|
static bool isbrace_space_or_nul(int c)
|
|
{
|
|
return isbrace(c) || isspace(c) || isnul(c);
|
|
}
|
|
|
|
static size_t tokenize_path_components(const char *str,
|
|
struct component *components,
|
|
size_t max_components)
|
|
{
|
|
size_t i = 0;
|
|
|
|
memset(components, 0, sizeof(*components) * max_components);
|
|
|
|
if (!str)
|
|
return 0;
|
|
|
|
while (*str && i < max_components) {
|
|
const char *fwdslash = strchrnul(str, '/');
|
|
|
|
/* Save the token if it is not "/" */
|
|
if (fwdslash != str) {
|
|
components[i].str = str;
|
|
components[i].len = fwdslash - str;
|
|
i++;
|
|
}
|
|
|
|
if (isnul(*fwdslash))
|
|
break;
|
|
|
|
str = fwdslash + 1;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* walk_one - walk one component of a features string
|
|
* @str: a pointer to the current position in a features string
|
|
* @component: the component to walk
|
|
* @is_top_level: true if component is a top-level component
|
|
*
|
|
* Imagine a features string such as:
|
|
*
|
|
* "feat1 { subfeat1.1 subfeat1.2 } feat2 { subfeat2.1 { subfeat2.1.1 } }"
|
|
*
|
|
* You want to know if "feat2/subfeat2.1/subfeat2.8" is valid. It will take 3
|
|
* invocations of this function to determine if that string is valid. On the
|
|
* first call, *@str will point to the beginning of the features string,
|
|
* component->str will be "feat2", and is_top_level will be true since feat2 is
|
|
* a top level feature. The function will return true and *@str will now point
|
|
* at the the left brace after "feat2" in the features string. You can call
|
|
* this function again with component->str being equal to "subfeat2.1" and it
|
|
* will return true and *@str will now point at the left brace after
|
|
* "subfeat2.1" in the features string. A third call to the function, with
|
|
* component->str equal to "subfeat2.8", will return false and *@str will not
|
|
* be changed.
|
|
*
|
|
* Returns true if the walk was successful and false otherwise. If the walk was
|
|
* successful, *@str will point to the first encountered brace after the walk.
|
|
* If the walk was unsuccessful, *@str is not updated.
|
|
*/
|
|
static bool walk_one(const char **str, const struct component *component,
|
|
bool is_top_level)
|
|
{
|
|
const char *cur;
|
|
uint32_t brace_count = 0;
|
|
size_t i = 0;
|
|
|
|
/* NULL pointers and empty strings are not accepted */
|
|
if (!str || !*str || !component || !component->str || !component->len)
|
|
return false;
|
|
|
|
cur = *str;
|
|
|
|
/**
|
|
* If @component is not top-level, the first character in the string to
|
|
* search MUST be '{'
|
|
*/
|
|
if (!is_top_level) {
|
|
if (!islbrace(*cur))
|
|
return false;
|
|
|
|
cur++;
|
|
}
|
|
|
|
/**
|
|
* This loop tries to find the @component in *@str. When this loops
|
|
* completes, cur will either point one character past the end of the
|
|
* matched @component or to the NUL terminator of *@str.
|
|
*/
|
|
while(!isnul(*cur) && i < component->len) {
|
|
if (!isascii(*cur)) {
|
|
/* Only ASCII is expected */
|
|
return false;
|
|
} else if (islbrace(*cur)) {
|
|
/* There's a limit to the number of left braces */
|
|
if (brace_count == UINT32_MAX)
|
|
return false;
|
|
|
|
brace_count++;
|
|
} else if (isrbrace(*cur)) {
|
|
/* Check for unexpected right braces */
|
|
if (brace_count == 0)
|
|
return false;
|
|
|
|
brace_count--;
|
|
}
|
|
|
|
/**
|
|
* Move to the next character in @component if we're not inside
|
|
* of braces and we have a character match
|
|
*/
|
|
if (brace_count == 0 && *cur == component->str[i])
|
|
i++;
|
|
else
|
|
i = 0;
|
|
|
|
cur++;
|
|
}
|
|
|
|
/* Return false if a full match was not found */
|
|
if (i != component->len) {
|
|
return false;
|
|
} else if (!isbrace_space_or_nul(*cur))
|
|
return false;
|
|
|
|
/**
|
|
* This loop eats up valid (ASCII) characters until a brace or NUL
|
|
* character is found so that *@str is properly set to call back into
|
|
* this function
|
|
*/
|
|
while (!isbrace_or_nul(*cur)) {
|
|
if (!isascii(*cur))
|
|
return false;
|
|
|
|
cur++;
|
|
}
|
|
|
|
*str = cur;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* aa_features_new - create a new features based on a path
|
|
* @features: will point to the address of an allocated and initialized
|
|
* aa_features object upon success
|
|
* @path: path to a features file or directory
|
|
*
|
|
* Returns: 0 on success, -1 on error with errno set and *@features pointing to
|
|
* NULL
|
|
*/
|
|
int aa_features_new(aa_features **features, const char *path)
|
|
{
|
|
struct stat stat_file;
|
|
aa_features *f;
|
|
int retval;
|
|
|
|
*features = NULL;
|
|
|
|
if (stat(path, &stat_file) == -1)
|
|
return -1;
|
|
|
|
f = (aa_features *) calloc(1, sizeof(*f));
|
|
if (!f) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
aa_features_ref(f);
|
|
|
|
retval = S_ISDIR(stat_file.st_mode) ?
|
|
handle_features_dir(path, f->string, STRING_SIZE, f->string) :
|
|
load_features_file(path, f->string, STRING_SIZE);
|
|
if (retval) {
|
|
int save = errno;
|
|
|
|
aa_features_unref(f);
|
|
errno = save;
|
|
return -1;
|
|
}
|
|
|
|
*features = f;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* aa_features_new_from_string - create a new features based on a string
|
|
* @features: will point to the address of an allocated and initialized
|
|
* aa_features object upon success
|
|
* @string: a NUL-terminated string representation of features
|
|
* @size: the size of @string, not counting the NUL-terminator
|
|
*
|
|
* Returns: 0 on success, -1 on error with errno set and *@features pointing to
|
|
* NULL
|
|
*/
|
|
int aa_features_new_from_string(aa_features **features,
|
|
const char *string, size_t size)
|
|
{
|
|
aa_features *f;
|
|
|
|
*features = NULL;
|
|
|
|
/* Require size to be less than STRING_SIZE so there's room for a NUL */
|
|
if (size >= STRING_SIZE)
|
|
return ENOBUFS;
|
|
|
|
f = (aa_features *) calloc(1, sizeof(*f));
|
|
if (!f) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
aa_features_ref(f);
|
|
|
|
memcpy(f->string, string, size);
|
|
f->string[size] = '\0';
|
|
*features = f;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* aa_features_new_from_kernel - create a new features based on the current kernel
|
|
* @features: will point to the address of an allocated and initialized
|
|
* aa_features object upon success
|
|
*
|
|
* Returns: 0 on success, -1 on error with errno set and *@features pointing to
|
|
* NULL
|
|
*/
|
|
int aa_features_new_from_kernel(aa_features **features)
|
|
{
|
|
return aa_features_new(features, FEATURES_FILE);
|
|
}
|
|
|
|
/**
|
|
* aa_features_ref - increments the ref count of a features
|
|
* @features: the features
|
|
*
|
|
* Returns: the features
|
|
*/
|
|
aa_features *aa_features_ref(aa_features *features)
|
|
{
|
|
atomic_inc(&features->ref_count);
|
|
return features;
|
|
}
|
|
|
|
/**
|
|
* aa_features_unref - decrements the ref count and frees the features when 0
|
|
* @features: the features (can be NULL)
|
|
*/
|
|
void aa_features_unref(aa_features *features)
|
|
{
|
|
if (features && atomic_dec_and_test(&features->ref_count))
|
|
free(features);
|
|
}
|
|
|
|
/**
|
|
* aa_features_write_to_file - write a string representation to a file
|
|
* @features: the features
|
|
* @path: the path to write to
|
|
*
|
|
* Returns: 0 on success, -1 on error with errno set
|
|
*/
|
|
int aa_features_write_to_file(aa_features *features, const char *path)
|
|
{
|
|
autoclose int fd = -1;
|
|
size_t size;
|
|
ssize_t retval;
|
|
char *string;
|
|
|
|
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC | O_CLOEXEC,
|
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
if (fd == -1)
|
|
return -1;
|
|
|
|
string = features->string;
|
|
size = strlen(string);
|
|
do {
|
|
retval = write(fd, string, size);
|
|
if (retval == -1)
|
|
return -1;
|
|
|
|
size -= retval;
|
|
string += retval;
|
|
} while (size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* aa_features_is_equal - equality test for two features
|
|
* @features1: the first features (can be NULL)
|
|
* @features2: the second features (can be NULL)
|
|
*
|
|
* Returns: true if they're equal, false if they're not or either are NULL
|
|
*/
|
|
bool aa_features_is_equal(aa_features *features1, aa_features *features2)
|
|
{
|
|
return features1 && features2 &&
|
|
strcmp(features1->string, features2->string) == 0;
|
|
}
|
|
|
|
/**
|
|
* aa_features_supports - provides features support status
|
|
* @features: the features
|
|
* @str: the string representation of a feature to check
|
|
*
|
|
* Example @str values are "dbus/mask/send", "caps/mask/audit_read", and
|
|
* "policy/versions/v7".
|
|
*
|
|
* Returns: a bool specifying the support status of @str feature
|
|
*/
|
|
bool aa_features_supports(aa_features *features, const char *str)
|
|
{
|
|
const char *features_string = features->string;
|
|
struct component components[32];
|
|
size_t i, num_components;
|
|
|
|
/* Empty strings are not accepted. Neither are leading '/' chars. */
|
|
if (!str || str[0] == '/')
|
|
return false;
|
|
|
|
/**
|
|
* Break @str into an array of components. For example,
|
|
* "mount/mask/mount" would turn into { "mount", 5 } as the first
|
|
* component, { "mask", 4 } as the second, and { "mount", 5 } as the
|
|
* third
|
|
*/
|
|
num_components = tokenize_path_components(str, components,
|
|
sizeof(components) / sizeof(*components));
|
|
|
|
/* At least one valid token is required */
|
|
if (!num_components)
|
|
return false;
|
|
|
|
/* Ensure that all components are valid and found */
|
|
for (i = 0; i < num_components; i++) {
|
|
if (!walk_one(&features_string, &components[i], i == 0))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef UNIT_TEST
|
|
|
|
#include "unit_test.h"
|
|
|
|
static int test_tokenize_path_components(void)
|
|
{
|
|
struct component components[32];
|
|
size_t max = sizeof(components) / sizeof(*components);
|
|
size_t num;
|
|
int rc = 0;
|
|
|
|
num = tokenize_path_components(NULL, components, max);
|
|
MY_TEST(num == 0, "basic NULL test");
|
|
|
|
num = tokenize_path_components("", components, max);
|
|
MY_TEST(num == 0, "basic empty string test");
|
|
|
|
num = tokenize_path_components("a", components, 0);
|
|
MY_TEST(num == 0, "basic empty array test");
|
|
|
|
num = tokenize_path_components("a", components, 1);
|
|
MY_TEST(num == 1, "one component full test (num)");
|
|
MY_TEST(!strncmp(components[0].str, "a", components[0].len),
|
|
"one component full test (components[0])");
|
|
|
|
num = tokenize_path_components("a/b", components, 2);
|
|
MY_TEST(num == 2, "two component full test (num)");
|
|
MY_TEST(!strncmp(components[0].str, "a", components[0].len),
|
|
"two component full test (components[0])");
|
|
MY_TEST(!strncmp(components[1].str, "b", components[0].len),
|
|
"two component full test (components[1])");
|
|
|
|
num = tokenize_path_components("a/b/c", components, 1);
|
|
MY_TEST(num == 1, "not enough components full test (num)");
|
|
MY_TEST(!strncmp(components[0].str, "a/b/c", components[0].len),
|
|
"not enough components full test (components[0])");
|
|
|
|
num = tokenize_path_components("/", components, max);
|
|
MY_TEST(num == 0, "no valid components #1 (num)");
|
|
|
|
num = tokenize_path_components("////////", components, max);
|
|
MY_TEST(num == 0, "no valid components #2 (num)");
|
|
|
|
num = tokenize_path_components("////////////foo////", components, max);
|
|
MY_TEST(num == 1, "many invalid components (num)");
|
|
MY_TEST(!strncmp(components[0].str, "foo", components[0].len),
|
|
"many invalid components (components[0])");
|
|
|
|
num = tokenize_path_components("file", components, max);
|
|
MY_TEST(num == 1, "file (num)");
|
|
MY_TEST(!strncmp(components[0].str, "file", components[0].len),
|
|
"file (components[0])");
|
|
|
|
num = tokenize_path_components("/policy///versions//v7/", components, max);
|
|
MY_TEST(num == 3, "v7 (num)");
|
|
MY_TEST(!strncmp(components[0].str, "policy", components[0].len),
|
|
"v7 (components[0])");
|
|
MY_TEST(!strncmp(components[1].str, "versions", components[1].len),
|
|
"v7 (components[1])");
|
|
MY_TEST(!strncmp(components[2].str, "v7", components[2].len),
|
|
"v7 (components[2])");
|
|
|
|
num = tokenize_path_components("dbus/mask/send", components, max);
|
|
MY_TEST(num == 3, "dbus send (num)");
|
|
MY_TEST(!strncmp(components[0].str, "dbus", components[0].len),
|
|
"dbus send (components[0])");
|
|
MY_TEST(!strncmp(components[1].str, "mask", components[1].len),
|
|
"dbus send (components[1])");
|
|
MY_TEST(!strncmp(components[2].str, "send", components[2].len),
|
|
"dbus send (components[2])");
|
|
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int do_test_walk_one(const char **str, const struct component *component,
|
|
bool is_top_level, bool expect_walk, const char *e1,
|
|
const char *e2, const char *e3)
|
|
{
|
|
const char *save = str ? *str : NULL;
|
|
bool walked = walk_one(str, component, is_top_level);
|
|
int rc = 0;
|
|
|
|
/* Check if the result of the walk matches the expected result*/
|
|
MY_TEST(expect_walk == walked, e1);
|
|
if (save) {
|
|
/**
|
|
* If a walk was expected, @*str should have changed. It
|
|
* shouldn't change if a walk was unexpected.
|
|
*/
|
|
if (expect_walk) {
|
|
MY_TEST(*str != save, e2);
|
|
} else {
|
|
MY_TEST(*str == save, e3);
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define MY_WALK_TEST(str, component, is_top_level, expect_walk, error) \
|
|
if (do_test_walk_one(str, component, is_top_level, \
|
|
expect_walk, \
|
|
error " (walk check)", \
|
|
error " (str didn't change)", \
|
|
error " (str changed)")) { \
|
|
rc = 1; \
|
|
}
|
|
|
|
#define MY_GOOD_WALK_TEST(str, component, is_top_level, error) \
|
|
MY_WALK_TEST(str, component, is_top_level, true, error)
|
|
#define MY_BAD_WALK_TEST(str, component, is_top_level, error) \
|
|
MY_WALK_TEST(str, component, is_top_level, false, error)
|
|
|
|
static int test_walk_one(void)
|
|
{
|
|
struct component c;
|
|
const char *str;
|
|
int rc = 0;
|
|
|
|
MY_BAD_WALK_TEST(NULL, &c, true, "basic NULL str test");
|
|
|
|
str = NULL;
|
|
MY_BAD_WALK_TEST(&str, &c, true, "basic NULL *str test");
|
|
|
|
str = "test { a b }";
|
|
MY_BAD_WALK_TEST(&str, NULL, true, "basic NULL component test");
|
|
|
|
str = "test { a b }";
|
|
c = { NULL, 8 };
|
|
MY_BAD_WALK_TEST(&str, &c, true, "basic NULL c.str test");
|
|
|
|
str = "test { a b }";
|
|
c = { "", 0 };
|
|
MY_BAD_WALK_TEST(&str, &c, true, "basic empty c.str test");
|
|
|
|
str = "test";
|
|
c = { "test", 4 };
|
|
MY_GOOD_WALK_TEST(&str, &c, true, "single component");
|
|
|
|
str = "testX";
|
|
c = { "test", 4 };
|
|
MY_BAD_WALK_TEST(&str, &c, true, "single component bad str");
|
|
|
|
str = "test";
|
|
c = { "testX", 5 };
|
|
MY_BAD_WALK_TEST(&str, &c, true, "single component bad c.str");
|
|
|
|
str = "test { }";
|
|
c = { "test", 4 };
|
|
MY_GOOD_WALK_TEST(&str, &c, true, "single component empty braces #1");
|
|
|
|
str = "test {\n\t}";
|
|
c = { "test", 4 };
|
|
MY_GOOD_WALK_TEST(&str, &c, true, "single component empty braces #2");
|
|
|
|
str = "test{}";
|
|
c = { "test", 4 };
|
|
MY_GOOD_WALK_TEST(&str, &c, true, "single component empty braces #3");
|
|
|
|
str = "test\t{}\n ";
|
|
c = { "test", 4 };
|
|
MY_GOOD_WALK_TEST(&str, &c, true, "single component empty braces #4");
|
|
|
|
str = "test {}";
|
|
c = { "test", 4 };
|
|
MY_BAD_WALK_TEST(&str, &c, false, "single component bad is_top_level");
|
|
|
|
str = "front{back";
|
|
c = { "frontback", 9};
|
|
MY_BAD_WALK_TEST(&str, &c, true, "brace in the middle #1");
|
|
MY_BAD_WALK_TEST(&str, &c, false, "brace in the middle #2");
|
|
|
|
str = "ardvark { bear cat { deer } }";
|
|
c = { "ardvark", 7 };
|
|
MY_GOOD_WALK_TEST(&str, &c, true, "animal walk good ardvark");
|
|
c = { "deer", 4 };
|
|
MY_BAD_WALK_TEST(&str, &c, false, "animal walk bad deer");
|
|
MY_BAD_WALK_TEST(&str, &c, true, "animal walk bad top-level deer");
|
|
c = { "bear", 4 };
|
|
MY_BAD_WALK_TEST(&str, &c, true, "animal walk bad bear");
|
|
c = { "cat", 3 };
|
|
MY_GOOD_WALK_TEST(&str, &c, false, "animal walk good cat");
|
|
c = { "ardvark", 7 };
|
|
MY_BAD_WALK_TEST(&str, &c, true, "animal walk bad ardvark");
|
|
c = { "deer", 4 };
|
|
MY_GOOD_WALK_TEST(&str, &c, false, "animal walk good deer");
|
|
|
|
str = "dbus {mask {acquire send receive\n}\n}\nsignal {mask {hup int\n}\n}";
|
|
c = { "hup", 3 };
|
|
MY_BAD_WALK_TEST(&str, &c, true, "dbus/signal bad hup #1");
|
|
MY_BAD_WALK_TEST(&str, &c, false, "dbus/signal bad hup #2");
|
|
c = { "signal", 6 };
|
|
MY_BAD_WALK_TEST(&str, &c, false, "dbus/signal bad signal");
|
|
MY_GOOD_WALK_TEST(&str, &c, true, "dbus/signal good signal");
|
|
c = { "mask", 4 };
|
|
MY_BAD_WALK_TEST(&str, &c, true, "dbus/signal bad mask");
|
|
MY_GOOD_WALK_TEST(&str, &c, false, "dbus/signal good mask");
|
|
c = { "hup", 3 };
|
|
MY_GOOD_WALK_TEST(&str, &c, false, "dbus/signal good hup");
|
|
|
|
str = "policy {set_load {yes\n}\nversions {v7 {yes\n}\nv6 {yes\n}";
|
|
c = { "policy", 6 };
|
|
MY_GOOD_WALK_TEST(&str, &c, true, "policy good");
|
|
c = { "versions", 8 };
|
|
MY_GOOD_WALK_TEST(&str, &c, false, "versions good");
|
|
c = { "v7", 2 };
|
|
MY_GOOD_WALK_TEST(&str, &c, false, "v7 good");
|
|
|
|
return rc;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int retval, rc = 0;
|
|
|
|
retval = test_tokenize_path_components();
|
|
if (retval)
|
|
rc = retval;
|
|
|
|
retval = test_walk_one();
|
|
if (retval)
|
|
rc = retval;
|
|
|
|
return rc;
|
|
}
|
|
|
|
#endif
|