| /* |
| * Copyright (c) 2013 Oracle and/or its affiliates. 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 would 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, write the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| * Author: |
| * Alexey Kodanev <alexey.kodanev@oracle.com> |
| * |
| * Test checks following preconditions: |
| * since Linux kernel 3.7 it is possible to set extended attributes |
| * to cgroup files. |
| */ |
| |
| #include <sys/stat.h> |
| #include <sys/mount.h> |
| #include <sys/types.h> |
| #include <sys/xattr.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| #include "test.h" |
| #include "safe_macros.h" |
| |
| char *TCID = "cgroup_xattr"; |
| |
| static const char subdir_name[] = "test"; |
| |
| #define MAX_SUBSYS 16 |
| #define MAX_OPTIONS_LEN 256 |
| #define MAX_DIR_NAME 64 |
| |
| /* struct to store available mount options */ |
| struct cgrp_option { |
| char str[MAX_OPTIONS_LEN]; |
| char dir[MAX_DIR_NAME]; |
| int hier; |
| int mounted; |
| int subdir; |
| }; |
| static struct cgrp_option cgrp_opt[MAX_SUBSYS]; |
| static int cgrp_opt_num; |
| |
| struct tst_key { |
| const char *name; |
| int good; |
| }; |
| |
| /* only security.* & trusted.* are valid key names */ |
| static const struct tst_key tkeys[] = { |
| { .name = "trusted.test", .good = 1, }, |
| { .name = "security.", .good = 1, }, |
| { .name = "user.", .good = 0, }, |
| { .name = "system.", .good = 0, }, |
| }; |
| |
| #define DEFAULT_VALUE_SIZE 8 |
| |
| /* struct to store key's value */ |
| struct tst_val { |
| char *buf; |
| size_t size; |
| }; |
| static struct tst_val val; |
| |
| /* it fills value's buffer */ |
| static char id; |
| |
| /* |
| * When test breaks, all open dirs should be closed |
| * otherwise umount won't succeed |
| */ |
| #define MAX_OPEN_DIR 32 |
| static DIR *odir[MAX_OPEN_DIR]; |
| static int odir_num; |
| |
| /* test options */ |
| static char *narg; |
| static int nflag; |
| static int skip_cleanup; |
| static int verbose; |
| static const option_t options[] = { |
| {"n:", &nflag, &narg}, |
| {"s", &skip_cleanup, NULL}, |
| {"v", &verbose, NULL}, |
| {NULL, NULL, NULL} |
| }; |
| |
| static void help(void); |
| static void setup(int argc, char *argv[]); |
| static void test_run(void); |
| static void cleanup(void); |
| |
| static int mount_cgroup(void); |
| static int set_xattrs(const char *file); |
| static int get_xattrs(const char *file); |
| /* |
| * set or get xattr recursively |
| * |
| * @path: start directory |
| * @xattr_operation: can be set_xattrs() or get_xattrs() |
| */ |
| static int cgrp_files_walking(const char *path, |
| int (*xattr_operation)(const char *)); |
| |
| int main(int argc, char *argv[]) |
| { |
| setup(argc, argv); |
| |
| test_run(); |
| |
| cleanup(); |
| |
| tst_exit(); |
| } |
| |
| static void help(void) |
| { |
| printf(" -n x Write x bytes to xattr value, default is %d\n", |
| DEFAULT_VALUE_SIZE); |
| printf(" -s Skip cleanup\n"); |
| printf(" -v Verbose\n"); |
| } |
| |
| void setup(int argc, char *argv[]) |
| { |
| const char *msg; |
| msg = parse_opts(argc, argv, options, help); |
| if (msg != NULL) |
| tst_brkm(TBROK, NULL, "OPTION PARSING ERROR - %s", msg); |
| |
| tst_require_root(NULL); |
| |
| if (access("/proc/cgroups", F_OK) == -1) |
| tst_brkm(TCONF, NULL, "Kernel doesn't support cgroups"); |
| |
| if (tst_kvercmp(3, 7, 0) < 0) { |
| tst_brkm(TCONF, NULL, |
| "Test must be run with kernel 3.7 or newer"); |
| } |
| |
| int value_size = DEFAULT_VALUE_SIZE; |
| if (nflag) { |
| if (sscanf(narg, "%i", &value_size) != 1) |
| tst_brkm(TBROK, NULL, "-n option arg is not a number"); |
| if (value_size <= 0) |
| tst_brkm(TBROK, NULL, "-n option arg is less than 1"); |
| } |
| |
| /* initialize test value */ |
| val.size = value_size; |
| val.buf = SAFE_MALLOC(NULL, value_size); |
| |
| tst_sig(FORK, DEF_HANDLER, cleanup); |
| |
| tst_tmpdir(); |
| |
| if (!mount_cgroup()) |
| tst_brkm(TCONF, cleanup, "Nothing mounted"); |
| } |
| |
| static void test_run(void) |
| { |
| int i, set_res = 0, get_res = 0; |
| |
| for (i = 0; i < cgrp_opt_num; ++i) { |
| if (!cgrp_opt[i].mounted) |
| continue; |
| |
| SAFE_CHDIR(cleanup, cgrp_opt[i].dir); |
| /* reset value */ |
| id = 0; |
| /* set xattr to each file in cgroup fs */ |
| set_res |= cgrp_files_walking(".", set_xattrs); |
| |
| id = 0; |
| /* get & check xattr */ |
| get_res |= cgrp_files_walking(".", get_xattrs); |
| SAFE_CHDIR(cleanup, ".."); |
| } |
| |
| /* final results */ |
| tst_resm(TINFO, "All test-cases have been completed, summary:"); |
| tst_resm(TINFO, "Set tests result: %s", (set_res) ? "FAIL" : "PASS"); |
| tst_resm(TINFO, "Get tests result: %s", (get_res) ? "FAIL" : "PASS"); |
| } |
| |
| static void cleanup(void) |
| { |
| if (val.buf != NULL) |
| free(val.buf); |
| |
| if (skip_cleanup) |
| return; |
| |
| /* |
| * Kernels 3.7 can crash while unmounting cgroups with xattr, |
| * call tst_flush() to make sure all buffered data written |
| * before it happens |
| */ |
| tst_flush(); |
| |
| int i; |
| for (i = 0; i < odir_num; ++i) { |
| if (closedir(odir[i]) == -1) |
| tst_brkm(TBROK, NULL, "Failed to close dir\n"); |
| } |
| |
| char *cwd = tst_get_tmpdir(); |
| SAFE_CHDIR(NULL, cwd); |
| free(cwd); |
| |
| for (i = 0; i < cgrp_opt_num; ++i) { |
| if (cgrp_opt[i].subdir) { |
| SAFE_CHDIR(NULL, cgrp_opt[i].dir); |
| if (rmdir(subdir_name) == -1) { |
| tst_brkm(TBROK | TERRNO, NULL, |
| "Can't remove dir"); |
| } |
| SAFE_CHDIR(NULL, ".."); |
| } |
| if (cgrp_opt[i].mounted) { |
| if (umount(cgrp_opt[i].dir) == -1) { |
| tst_brkm(TBROK | TERRNO, NULL, |
| "Can't unmount: %s", cgrp_opt[i].dir); |
| } |
| } |
| } |
| |
| tst_rmdir(); |
| } |
| |
| int mount_cgroup(void) |
| { |
| FILE *fd = fopen("/proc/cgroups", "r"); |
| if (fd == NULL) |
| tst_brkm(TBROK, cleanup, "Failed to read /proc/cgroups"); |
| char str[MAX_DIR_NAME], name[MAX_DIR_NAME]; |
| int hier = 0, num = 0, enabled = 0, first = 1; |
| /* make mount options */ |
| while ((fgets(str, MAX_DIR_NAME, fd)) != NULL) { |
| /* skip first line */ |
| if (first) { |
| first = 0; |
| continue; |
| } |
| if (sscanf(str, "%s\t%d\t%d\t%d", |
| name, &hier, &num, &enabled) != 4) |
| tst_brkm(TBROK, cleanup, "Can't parse /proc/cgroups"); |
| if (!enabled) |
| continue; |
| |
| /* BUG WORKAROUND |
| * Only mount those subsystems, which are not mounted yet. |
| * It's a workaround to a bug when mount doesn't return any err |
| * code while mounting already mounted subsystems, but with |
| * additional "xattr" option. In that case, mount will succeed, |
| * but xattr won't be supported in the new mount anyway. |
| * Should be removed as soon as a fix committed to upstream. |
| */ |
| if (hier != 0) |
| continue; |
| |
| int i, found = 0; |
| for (i = 0; i < cgrp_opt_num; ++i) { |
| if (cgrp_opt[i].hier == hier) { |
| found = 1; |
| break; |
| } |
| } |
| if (!found) { |
| i = cgrp_opt_num++; |
| cgrp_opt[i].hier = hier; |
| } |
| char *str = cgrp_opt[i].str; |
| if (str[0] == '\0') |
| strcpy(str, "xattr"); |
| snprintf(str + strlen(str), MAX_OPTIONS_LEN - strlen(str), |
| ",%s", name); |
| } |
| fclose(fd); |
| |
| int i, any_mounted = 0; |
| for (i = 0; i < cgrp_opt_num; ++i) { |
| char dir[MAX_DIR_NAME]; |
| struct cgrp_option *opt = &cgrp_opt[i]; |
| tst_resm(TINFO, "mount options %d: %s (hier = %d)", |
| i, opt->str, opt->hier); |
| snprintf(opt->dir, MAX_DIR_NAME, "cgx_%d", opt->hier); |
| SAFE_MKDIR(cleanup, opt->dir, 0755); |
| |
| if (mount(opt->dir, opt->dir, "cgroup", 0, opt->str) == -1) { |
| tst_resm(TINFO, "Can't mount: %s", dir); |
| continue; |
| } |
| |
| any_mounted = 1; |
| opt->mounted = 1; |
| |
| /* create new hierarchy */ |
| SAFE_CHDIR(cleanup, opt->dir); |
| SAFE_MKDIR(cleanup, subdir_name, 0755); |
| opt->subdir = 1; |
| SAFE_CHDIR(cleanup, ".."); |
| } |
| return any_mounted; |
| } |
| |
| static int set_xattrs(const char *file) |
| { |
| int i, err, fail, res = 0; |
| |
| for (i = 0; i < ARRAY_SIZE(tkeys); ++i) { |
| err = setxattr(file, tkeys[i].name, |
| (const void *)val.buf, val.size, 0) == -1; |
| |
| fail = err && tkeys[i].good; |
| res |= fail; |
| |
| tst_resm((fail) ? TFAIL : TPASS, |
| "Expect: %s set xattr key '%s' to file '%s'", |
| (tkeys[i].good) ? "can" : "can't", |
| tkeys[i].name, file); |
| |
| if (verbose && tkeys[i].good) |
| tst_resm_hexd(TINFO, val.buf, val.size, "value:"); |
| } |
| return res; |
| } |
| |
| static int get_xattrs(const char *file) |
| { |
| int i, fail, res = 0; |
| |
| for (i = 0; i < ARRAY_SIZE(tkeys); ++i) { |
| /* get value size */ |
| ssize_t size = getxattr(file, tkeys[i].name, NULL, 0); |
| fail = (size == -1 && tkeys[i].good); |
| res |= fail; |
| |
| tst_resm((fail) ? TFAIL : TPASS, |
| "Expect: %s read xattr %s of file '%s'", |
| (tkeys[i].good) ? "can" : "can't", |
| tkeys[i].name, file); |
| |
| if (fail || size == -1) |
| continue; |
| |
| /* get xattr value */ |
| char xval[size]; |
| if (getxattr(file, tkeys[i].name, xval, size) == -1) { |
| tst_brkm(TBROK, cleanup, |
| "Can't get buffer of key %s", |
| tkeys[i].name); |
| } |
| fail = val.size != size || |
| strncmp(val.buf, xval, val.size) != 0; |
| res |= fail; |
| |
| tst_resm((fail) ? TFAIL : TPASS, "Expect: values equal"); |
| |
| if (verbose && fail) { |
| tst_resm_hexd(TINFO, xval, size, |
| "Read xattr value:"); |
| tst_resm_hexd(TINFO, val.buf, val.size, |
| "Expect xattr value:"); |
| } |
| } |
| return res; |
| } |
| |
| static int cgrp_files_walking(const char *path, |
| int (*xattr_operation)(const char *)) |
| { |
| int res = 0; |
| struct dirent *entry; |
| DIR *dir = opendir(path); |
| |
| odir[odir_num] = dir; |
| if (++odir_num >= MAX_OPEN_DIR) { |
| tst_brkm(TBROK, cleanup, |
| "Unexpected num of open dirs, max: %d", MAX_OPEN_DIR); |
| } |
| |
| SAFE_CHDIR(cleanup, path); |
| |
| tst_resm(TINFO, "In dir %s", path); |
| |
| errno = 0; |
| while ((entry = readdir(dir)) != NULL) { |
| const char *file = entry->d_name; |
| /* skip current and up directories */ |
| if (!strcmp(file, "..") || !strcmp(file, ".")) |
| continue; |
| struct stat stat_buf; |
| TEST(lstat(file, &stat_buf)); |
| if (TEST_RETURN != -1 && S_ISDIR(stat_buf.st_mode)) { |
| /* proceed to subdir */ |
| res |= cgrp_files_walking(file, xattr_operation); |
| tst_resm(TINFO, "In dir %s", path); |
| } |
| memset(val.buf, id++, val.size); |
| res |= xattr_operation(file); |
| errno = 0; |
| } |
| if (errno && !entry) { |
| tst_brkm(TWARN | TERRNO, cleanup, |
| "Error while reading dir '%s'", path); |
| } |
| if (closedir(dir) == -1) |
| tst_brkm(TWARN, cleanup, "Failed to close dir '%s'", path); |
| else |
| odir[--odir_num] = NULL; |
| |
| if (strcmp(path, ".")) |
| SAFE_CHDIR(cleanup, ".."); |
| return res; |
| } |