717 lines
16 KiB
C
717 lines
16 KiB
C
/*
|
|
* CDDL HEADER START
|
|
*
|
|
* The contents of this file are subject to the terms of the
|
|
* Common Development and Distribution License (the "License").
|
|
* You may not use this file except in compliance with the License.
|
|
*
|
|
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
|
* or http://www.opensolaris.org/os/licensing.
|
|
* See the License for the specific language governing permissions
|
|
* and limitations under the License.
|
|
*
|
|
* When distributing Covered Code, include this CDDL HEADER in each
|
|
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
|
* If applicable, add the following below this CDDL HEADER, with the
|
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
|
* information: Portions Copyright [yyyy] [name of copyright owner]
|
|
*
|
|
* CDDL HEADER END
|
|
*/
|
|
|
|
/*
|
|
* Copyright 2016 Lawrence Livermore National Security, LLC.
|
|
*/
|
|
|
|
/*
|
|
* An extended attribute (xattr) correctness test. This program creates
|
|
* N files and sets M attrs on them of size S. Optionally is will verify
|
|
* a pattern stored in the xattr.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <sys/xattr.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <linux/limits.h>
|
|
|
|
#define ERROR(fmt, ...) \
|
|
fprintf(stderr, "xattrtest: %s:%d: %s: " fmt "\n", \
|
|
__FILE__, __LINE__, \
|
|
__func__, ## __VA_ARGS__);
|
|
|
|
static const char shortopts[] = "hvycdn:f:x:s:p:t:e:rRko:";
|
|
static const struct option longopts[] = {
|
|
{ "help", no_argument, 0, 'h' },
|
|
{ "verbose", no_argument, 0, 'v' },
|
|
{ "verify", no_argument, 0, 'y' },
|
|
{ "nth", required_argument, 0, 'n' },
|
|
{ "files", required_argument, 0, 'f' },
|
|
{ "xattrs", required_argument, 0, 'x' },
|
|
{ "size", required_argument, 0, 's' },
|
|
{ "path", required_argument, 0, 'p' },
|
|
{ "synccaches", no_argument, 0, 'c' },
|
|
{ "dropcaches", no_argument, 0, 'd' },
|
|
{ "script", required_argument, 0, 't' },
|
|
{ "seed", required_argument, 0, 'e' },
|
|
{ "random", no_argument, 0, 'r' },
|
|
{ "randomvalue", no_argument, 0, 'R' },
|
|
{ "keep", no_argument, 0, 'k' },
|
|
{ "only", required_argument, 0, 'o' },
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
|
|
enum phases {
|
|
PHASE_ALL = 0,
|
|
PHASE_CREATE,
|
|
PHASE_SETXATTR,
|
|
PHASE_GETXATTR,
|
|
PHASE_UNLINK,
|
|
PHASE_INVAL
|
|
};
|
|
|
|
static int verbose = 0;
|
|
static int verify = 0;
|
|
static int synccaches = 0;
|
|
static int dropcaches = 0;
|
|
static int nth = 0;
|
|
static int files = 1000;
|
|
static int xattrs = 1;
|
|
static int size = 6;
|
|
static int size_is_random = 0;
|
|
static int value_is_random = 0;
|
|
static int keep_files = 0;
|
|
static int phase = PHASE_ALL;
|
|
static char path[PATH_MAX] = "/tmp/xattrtest";
|
|
static char script[PATH_MAX] = "/bin/true";
|
|
static char xattrbytes[XATTR_SIZE_MAX];
|
|
|
|
static int
|
|
usage(char *argv0)
|
|
{
|
|
fprintf(stderr,
|
|
"usage: %s [-hvycdrRk] [-n <nth>] [-f <files>] [-x <xattrs>]\n"
|
|
" [-s <bytes>] [-p <path>] [-t <script> ] [-o <phase>]\n",
|
|
argv0);
|
|
|
|
fprintf(stderr,
|
|
" --help -h This help\n"
|
|
" --verbose -v Increase verbosity\n"
|
|
" --verify -y Verify xattr contents\n"
|
|
" --nth -n <nth> Print every nth file\n"
|
|
" --files -f <files> Set xattrs on N files\n"
|
|
" --xattrs -x <xattrs> Set N xattrs on each file\n"
|
|
" --size -s <bytes> Set N bytes per xattr\n"
|
|
" --path -p <path> Path to files\n"
|
|
" --synccaches -c Sync caches between phases\n"
|
|
" --dropcaches -d Drop caches between phases\n"
|
|
" --script -t <script> Exec script between phases\n"
|
|
" --seed -e <seed> Random seed value\n"
|
|
" --random -r Randomly sized xattrs [16-size]\n"
|
|
" --randomvalue -R Random xattr values\n"
|
|
" --keep -k Don't unlink files\n"
|
|
" --only -o <num> Only run phase N\n"
|
|
" 0=all, 1=create, 2=setxattr,\n"
|
|
" 3=getxattr, 4=unlink\n\n");
|
|
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
parse_args(int argc, char **argv)
|
|
{
|
|
long seed = time(NULL);
|
|
int c;
|
|
int rc = 0;
|
|
|
|
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
|
|
switch (c) {
|
|
case 'h':
|
|
return (usage(argv[0]));
|
|
case 'v':
|
|
verbose++;
|
|
break;
|
|
case 'y':
|
|
verify = 1;
|
|
break;
|
|
case 'n':
|
|
nth = strtol(optarg, NULL, 0);
|
|
break;
|
|
case 'f':
|
|
files = strtol(optarg, NULL, 0);
|
|
break;
|
|
case 'x':
|
|
xattrs = strtol(optarg, NULL, 0);
|
|
break;
|
|
case 's':
|
|
size = strtol(optarg, NULL, 0);
|
|
if (size > XATTR_SIZE_MAX) {
|
|
fprintf(stderr, "Error: the -s value may not "
|
|
"be greater than %d\n", XATTR_SIZE_MAX);
|
|
rc = 1;
|
|
}
|
|
break;
|
|
case 'p':
|
|
strncpy(path, optarg, PATH_MAX);
|
|
path[PATH_MAX - 1] = '\0';
|
|
break;
|
|
case 'c':
|
|
synccaches = 1;
|
|
break;
|
|
case 'd':
|
|
dropcaches = 1;
|
|
break;
|
|
case 't':
|
|
strncpy(script, optarg, PATH_MAX);
|
|
script[PATH_MAX - 1] = '\0';
|
|
break;
|
|
case 'e':
|
|
seed = strtol(optarg, NULL, 0);
|
|
break;
|
|
case 'r':
|
|
size_is_random = 1;
|
|
break;
|
|
case 'R':
|
|
value_is_random = 1;
|
|
break;
|
|
case 'k':
|
|
keep_files = 1;
|
|
break;
|
|
case 'o':
|
|
phase = strtol(optarg, NULL, 0);
|
|
if (phase <= PHASE_ALL || phase >= PHASE_INVAL) {
|
|
fprintf(stderr, "Error: the -o value must be "
|
|
"greater than %d and less than %d\n",
|
|
PHASE_ALL, PHASE_INVAL);
|
|
rc = 1;
|
|
}
|
|
break;
|
|
default:
|
|
rc = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rc != 0)
|
|
return (rc);
|
|
|
|
srandom(seed);
|
|
|
|
if (verbose) {
|
|
fprintf(stdout, "verbose: %d\n", verbose);
|
|
fprintf(stdout, "verify: %d\n", verify);
|
|
fprintf(stdout, "nth: %d\n", nth);
|
|
fprintf(stdout, "files: %d\n", files);
|
|
fprintf(stdout, "xattrs: %d\n", xattrs);
|
|
fprintf(stdout, "size: %d\n", size);
|
|
fprintf(stdout, "path: %s\n", path);
|
|
fprintf(stdout, "synccaches: %d\n", synccaches);
|
|
fprintf(stdout, "dropcaches: %d\n", dropcaches);
|
|
fprintf(stdout, "script: %s\n", script);
|
|
fprintf(stdout, "seed: %ld\n", seed);
|
|
fprintf(stdout, "random size: %d\n", size_is_random);
|
|
fprintf(stdout, "random value: %d\n", value_is_random);
|
|
fprintf(stdout, "keep: %d\n", keep_files);
|
|
fprintf(stdout, "only: %d\n", phase);
|
|
fprintf(stdout, "%s", "\n");
|
|
}
|
|
|
|
return (rc);
|
|
}
|
|
|
|
static int
|
|
drop_caches(void)
|
|
{
|
|
char file[] = "/proc/sys/vm/drop_caches";
|
|
int fd, rc;
|
|
|
|
fd = open(file, O_WRONLY);
|
|
if (fd == -1) {
|
|
ERROR("Error %d: open(\"%s\", O_WRONLY)\n", errno, file);
|
|
return (errno);
|
|
}
|
|
|
|
rc = write(fd, "3", 1);
|
|
if ((rc == -1) || (rc != 1)) {
|
|
ERROR("Error %d: write(%d, \"3\", 1)\n", errno, fd);
|
|
(void) close(fd);
|
|
return (errno);
|
|
}
|
|
|
|
rc = close(fd);
|
|
if (rc == -1) {
|
|
ERROR("Error %d: close(%d)\n", errno, fd);
|
|
return (errno);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
run_process(const char *path, char *argv[])
|
|
{
|
|
pid_t pid;
|
|
int rc, devnull_fd;
|
|
|
|
pid = fork();
|
|
if (pid == 0) {
|
|
devnull_fd = open("/dev/null", O_WRONLY);
|
|
|
|
if (devnull_fd < 0)
|
|
_exit(-1);
|
|
|
|
(void) dup2(devnull_fd, STDOUT_FILENO);
|
|
(void) dup2(devnull_fd, STDERR_FILENO);
|
|
close(devnull_fd);
|
|
|
|
(void) execvp(path, argv);
|
|
_exit(-1);
|
|
} else if (pid > 0) {
|
|
int status;
|
|
|
|
while ((rc = waitpid(pid, &status, 0)) == -1 &&
|
|
errno == EINTR) { }
|
|
|
|
if (rc < 0 || !WIFEXITED(status))
|
|
return (-1);
|
|
|
|
return (WEXITSTATUS(status));
|
|
}
|
|
|
|
return (-1);
|
|
}
|
|
|
|
static int
|
|
post_hook(char *phase)
|
|
{
|
|
char *argv[3] = { script, phase, (char *)0 };
|
|
int rc;
|
|
|
|
if (synccaches)
|
|
sync();
|
|
|
|
if (dropcaches) {
|
|
rc = drop_caches();
|
|
if (rc)
|
|
return (rc);
|
|
}
|
|
|
|
rc = run_process(script, argv);
|
|
if (rc)
|
|
return (rc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
#define USEC_PER_SEC 1000000
|
|
|
|
static void
|
|
timeval_normalize(struct timeval *tv, time_t sec, suseconds_t usec)
|
|
{
|
|
while (usec >= USEC_PER_SEC) {
|
|
usec -= USEC_PER_SEC;
|
|
sec++;
|
|
}
|
|
|
|
while (usec < 0) {
|
|
usec += USEC_PER_SEC;
|
|
sec--;
|
|
}
|
|
|
|
tv->tv_sec = sec;
|
|
tv->tv_usec = usec;
|
|
}
|
|
|
|
static void
|
|
timeval_sub(struct timeval *delta, struct timeval *tv1, struct timeval *tv2)
|
|
{
|
|
timeval_normalize(delta,
|
|
tv1->tv_sec - tv2->tv_sec,
|
|
tv1->tv_usec - tv2->tv_usec);
|
|
}
|
|
|
|
static double
|
|
timeval_sub_seconds(struct timeval *tv1, struct timeval *tv2)
|
|
{
|
|
struct timeval delta;
|
|
|
|
timeval_sub(&delta, tv1, tv2);
|
|
return ((double)delta.tv_usec / USEC_PER_SEC + delta.tv_sec);
|
|
}
|
|
|
|
static int
|
|
create_files(void)
|
|
{
|
|
int i, rc;
|
|
char *file = NULL;
|
|
struct timeval start, stop;
|
|
double seconds;
|
|
size_t fsize;
|
|
|
|
fsize = PATH_MAX;
|
|
file = malloc(fsize);
|
|
if (file == NULL) {
|
|
rc = ENOMEM;
|
|
ERROR("Error %d: malloc(%d) bytes for file name\n", rc,
|
|
PATH_MAX);
|
|
goto out;
|
|
}
|
|
|
|
(void) gettimeofday(&start, NULL);
|
|
|
|
for (i = 1; i <= files; i++) {
|
|
if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
|
|
rc = EINVAL;
|
|
ERROR("Error %d: path too long\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
if (nth && ((i % nth) == 0))
|
|
fprintf(stdout, "create: %s\n", file);
|
|
|
|
rc = unlink(file);
|
|
if ((rc == -1) && (errno != ENOENT)) {
|
|
ERROR("Error %d: unlink(%s)\n", errno, file);
|
|
rc = errno;
|
|
goto out;
|
|
}
|
|
|
|
rc = open(file, O_CREAT, 0644);
|
|
if (rc == -1) {
|
|
ERROR("Error %d: open(%s, O_CREATE, 0644)\n",
|
|
errno, file);
|
|
rc = errno;
|
|
goto out;
|
|
}
|
|
|
|
rc = close(rc);
|
|
if (rc == -1) {
|
|
ERROR("Error %d: close(%d)\n", errno, rc);
|
|
rc = errno;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
(void) gettimeofday(&stop, NULL);
|
|
seconds = timeval_sub_seconds(&stop, &start);
|
|
fprintf(stdout, "create: %f seconds %f creates/second\n",
|
|
seconds, files / seconds);
|
|
|
|
rc = post_hook("post");
|
|
out:
|
|
if (file)
|
|
free(file);
|
|
|
|
return (rc);
|
|
}
|
|
|
|
static int
|
|
get_random_bytes(char *buf, size_t bytes)
|
|
{
|
|
int rand;
|
|
ssize_t bytes_read = 0;
|
|
|
|
rand = open("/dev/urandom", O_RDONLY);
|
|
|
|
if (rand < 0)
|
|
return (rand);
|
|
|
|
while (bytes_read < bytes) {
|
|
ssize_t rc = read(rand, buf + bytes_read, bytes - bytes_read);
|
|
if (rc < 0)
|
|
break;
|
|
bytes_read += rc;
|
|
}
|
|
|
|
(void) close(rand);
|
|
|
|
return (bytes_read);
|
|
}
|
|
|
|
static int
|
|
setxattrs(void)
|
|
{
|
|
int i, j, rnd_size = size, shift, rc = 0;
|
|
char name[XATTR_NAME_MAX];
|
|
char *value = NULL;
|
|
char *file = NULL;
|
|
struct timeval start, stop;
|
|
double seconds;
|
|
size_t fsize;
|
|
|
|
value = malloc(XATTR_SIZE_MAX);
|
|
if (value == NULL) {
|
|
rc = ENOMEM;
|
|
ERROR("Error %d: malloc(%d) bytes for xattr value\n", rc,
|
|
XATTR_SIZE_MAX);
|
|
goto out;
|
|
}
|
|
|
|
fsize = PATH_MAX;
|
|
file = malloc(fsize);
|
|
if (file == NULL) {
|
|
rc = ENOMEM;
|
|
ERROR("Error %d: malloc(%d) bytes for file name\n", rc,
|
|
PATH_MAX);
|
|
goto out;
|
|
}
|
|
|
|
(void) gettimeofday(&start, NULL);
|
|
|
|
for (i = 1; i <= files; i++) {
|
|
if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
|
|
rc = EINVAL;
|
|
ERROR("Error %d: path too long\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
if (nth && ((i % nth) == 0))
|
|
fprintf(stdout, "setxattr: %s\n", file);
|
|
|
|
for (j = 1; j <= xattrs; j++) {
|
|
if (size_is_random)
|
|
rnd_size = (random() % (size - 16)) + 16;
|
|
|
|
(void) sprintf(name, "user.%d", j);
|
|
shift = sprintf(value, "size=%d ", rnd_size);
|
|
memcpy(value + shift, xattrbytes,
|
|
sizeof (xattrbytes) - shift);
|
|
|
|
rc = lsetxattr(file, name, value, rnd_size, 0);
|
|
if (rc == -1) {
|
|
ERROR("Error %d: lsetxattr(%s, %s, ..., %d)\n",
|
|
errno, file, name, rnd_size);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
(void) gettimeofday(&stop, NULL);
|
|
seconds = timeval_sub_seconds(&stop, &start);
|
|
fprintf(stdout, "setxattr: %f seconds %f setxattrs/second\n",
|
|
seconds, (files * xattrs) / seconds);
|
|
|
|
rc = post_hook("post");
|
|
out:
|
|
if (file)
|
|
free(file);
|
|
|
|
if (value)
|
|
free(value);
|
|
|
|
return (rc);
|
|
}
|
|
|
|
static int
|
|
getxattrs(void)
|
|
{
|
|
int i, j, rnd_size, shift, rc = 0;
|
|
char name[XATTR_NAME_MAX];
|
|
char *verify_value = NULL;
|
|
char *verify_string;
|
|
char *value = NULL;
|
|
char *value_string;
|
|
char *file = NULL;
|
|
struct timeval start, stop;
|
|
double seconds;
|
|
size_t fsize;
|
|
|
|
verify_value = malloc(XATTR_SIZE_MAX);
|
|
if (verify_value == NULL) {
|
|
rc = ENOMEM;
|
|
ERROR("Error %d: malloc(%d) bytes for xattr verify\n", rc,
|
|
XATTR_SIZE_MAX);
|
|
goto out;
|
|
}
|
|
|
|
value = malloc(XATTR_SIZE_MAX);
|
|
if (value == NULL) {
|
|
rc = ENOMEM;
|
|
ERROR("Error %d: malloc(%d) bytes for xattr value\n", rc,
|
|
XATTR_SIZE_MAX);
|
|
goto out;
|
|
}
|
|
|
|
verify_string = value_is_random ? "<random>" : verify_value;
|
|
value_string = value_is_random ? "<random>" : value;
|
|
|
|
fsize = PATH_MAX;
|
|
file = malloc(fsize);
|
|
|
|
if (file == NULL) {
|
|
rc = ENOMEM;
|
|
ERROR("Error %d: malloc(%d) bytes for file name\n", rc,
|
|
PATH_MAX);
|
|
goto out;
|
|
}
|
|
|
|
(void) gettimeofday(&start, NULL);
|
|
|
|
for (i = 1; i <= files; i++) {
|
|
if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
|
|
rc = EINVAL;
|
|
ERROR("Error %d: path too long\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
if (nth && ((i % nth) == 0))
|
|
fprintf(stdout, "getxattr: %s\n", file);
|
|
|
|
for (j = 1; j <= xattrs; j++) {
|
|
(void) sprintf(name, "user.%d", j);
|
|
|
|
rc = lgetxattr(file, name, value, XATTR_SIZE_MAX);
|
|
if (rc == -1) {
|
|
ERROR("Error %d: lgetxattr(%s, %s, ..., %d)\n",
|
|
errno, file, name, XATTR_SIZE_MAX);
|
|
goto out;
|
|
}
|
|
|
|
if (!verify)
|
|
continue;
|
|
|
|
sscanf(value, "size=%d [a-z]", &rnd_size);
|
|
shift = sprintf(verify_value, "size=%d ",
|
|
rnd_size);
|
|
memcpy(verify_value + shift, xattrbytes,
|
|
sizeof (xattrbytes) - shift);
|
|
|
|
if (rnd_size != rc ||
|
|
memcmp(verify_value, value, rnd_size)) {
|
|
ERROR("Error %d: verify failed\n "
|
|
"verify: %s\n value: %s\n", EINVAL,
|
|
verify_string, value_string);
|
|
rc = 1;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
(void) gettimeofday(&stop, NULL);
|
|
seconds = timeval_sub_seconds(&stop, &start);
|
|
fprintf(stdout, "getxattr: %f seconds %f getxattrs/second\n",
|
|
seconds, (files * xattrs) / seconds);
|
|
|
|
rc = post_hook("post");
|
|
out:
|
|
if (file)
|
|
free(file);
|
|
|
|
if (value)
|
|
free(value);
|
|
|
|
if (verify_value)
|
|
free(verify_value);
|
|
|
|
return (rc);
|
|
}
|
|
|
|
static int
|
|
unlink_files(void)
|
|
{
|
|
int i, rc;
|
|
char *file = NULL;
|
|
struct timeval start, stop;
|
|
double seconds;
|
|
size_t fsize;
|
|
|
|
fsize = PATH_MAX;
|
|
file = malloc(fsize);
|
|
if (file == NULL) {
|
|
rc = ENOMEM;
|
|
ERROR("Error %d: malloc(%d) bytes for file name\n",
|
|
rc, PATH_MAX);
|
|
goto out;
|
|
}
|
|
|
|
(void) gettimeofday(&start, NULL);
|
|
|
|
for (i = 1; i <= files; i++) {
|
|
if (snprintf(file, fsize, "%s/file-%d", path, i) >= fsize) {
|
|
rc = EINVAL;
|
|
ERROR("Error %d: path too long\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
if (nth && ((i % nth) == 0))
|
|
fprintf(stdout, "unlink: %s\n", file);
|
|
|
|
rc = unlink(file);
|
|
if ((rc == -1) && (errno != ENOENT)) {
|
|
ERROR("Error %d: unlink(%s)\n", errno, file);
|
|
free(file);
|
|
return (errno);
|
|
}
|
|
}
|
|
|
|
(void) gettimeofday(&stop, NULL);
|
|
seconds = timeval_sub_seconds(&stop, &start);
|
|
fprintf(stdout, "unlink: %f seconds %f unlinks/second\n",
|
|
seconds, files / seconds);
|
|
|
|
rc = post_hook("post");
|
|
out:
|
|
if (file)
|
|
free(file);
|
|
|
|
return (rc);
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int rc;
|
|
|
|
rc = parse_args(argc, argv);
|
|
if (rc)
|
|
return (rc);
|
|
|
|
if (value_is_random) {
|
|
size_t rndsz = sizeof (xattrbytes);
|
|
|
|
rc = get_random_bytes(xattrbytes, rndsz);
|
|
if (rc < rndsz) {
|
|
ERROR("Error %d: get_random_bytes() wanted %zd "
|
|
"got %d\n", errno, rndsz, rc);
|
|
return (rc);
|
|
}
|
|
} else {
|
|
memset(xattrbytes, 'x', sizeof (xattrbytes));
|
|
}
|
|
|
|
if (phase == PHASE_ALL || phase == PHASE_CREATE) {
|
|
rc = create_files();
|
|
if (rc)
|
|
return (rc);
|
|
}
|
|
|
|
if (phase == PHASE_ALL || phase == PHASE_SETXATTR) {
|
|
rc = setxattrs();
|
|
if (rc)
|
|
return (rc);
|
|
}
|
|
|
|
if (phase == PHASE_ALL || phase == PHASE_GETXATTR) {
|
|
rc = getxattrs();
|
|
if (rc)
|
|
return (rc);
|
|
}
|
|
|
|
if (!keep_files && (phase == PHASE_ALL || phase == PHASE_UNLINK)) {
|
|
rc = unlink_files();
|
|
if (rc)
|
|
return (rc);
|
|
}
|
|
|
|
return (0);
|
|
}
|