/*
* pseudo.c, main pseudo utility program
*
* Copyright (c) 2008-2013 Wind River Systems, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-only
*
*/
#include <stdlib.h>
#include <ctype.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <limits.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include "pseudo.h"
#include "pseudo_ipc.h"
#include "pseudo_client.h"
#include "pseudo_server.h"
#include "pseudo_db.h"
int opt_B = 0;
int opt_C = 0;
int opt_d = 0;
int opt_f = 0;
char *opt_i = NULL;
int opt_l = 0;
char *opt_m = NULL;
char *opt_M = NULL;
long opt_p = 0;
char *opt_r = NULL;
int opt_S = 0;
static int pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag, char **response_path, size_t *response_len);
static int pseudo_db_check(int fix);
void
usage(int status) {
FILE *f = status ? stderr : stdout;
fputs("Usage: pseudo [-dflv] [-x flags] [-P prefix] [-rR root] [-t timeout] [command]\n", f);
fputs(" pseudo -h\n", f);
fputs(" pseudo [-dflv] [-x flags] [-P prefix] [-BC] -i path\n", f);
fputs(" pseudo [-dflv] [-x flags] [-P prefix] [-BC] -m from -M to\n", f);
fputs(" pseudo [-dflv] [-x flags] [-P prefix] -C\n", f);
fputs(" pseudo [-dflv] [-x flags] [-P prefix] -S\n", f);
fputs(" pseudo [-dflv] [-x flags] [-P prefix] -V\n", f);
fputs("Debugging flags:\n", f);
for (int i = 1; i < PDBG_MAX; i += 2) {
unsigned char symbolics[2];
const char *descriptions[2];
symbolics[0] = pseudo_debug_type_symbolic(i);
symbolics[1] = pseudo_debug_type_symbolic(i + 1);
descriptions[0] = pseudo_debug_type_description(i);
descriptions[1] = pseudo_debug_type_description(i + 1);
if (symbolics[1]) {
fprintf(f, " %c %-32s %c %-32s\n",
symbolics[0], descriptions[0],
symbolics[1], descriptions[1]);
} else {
fprintf(f, " %c %-32s\n",
symbolics[0], descriptions[0]);
}
}
exit(status);
}
/* main server process */
int
main(int argc, char *argv[]) {
int o;
char *s;
char *ld_env = getenv(PRELINK_LIBRARIES);
int rc = 0;
char opts[pseudo_path_max()], *optptr = opts;
sigset_t blocked, saved;
opts[0] = '\0';
pseudo_init_util();
/* The pseudo client will have blocked these, and sigprocmask
* is inherited, but we want them to work.
*/
sigemptyset(&blocked);
sigaddset(&blocked, SIGALRM); /* every-N-seconds tasks */
sigaddset(&blocked, SIGCHLD); /* reaping child processes */
sigaddset(&blocked, SIGHUP); /* idiomatically, reloading config */
sigaddset(&blocked, SIGTERM); /* shutdown/teardown operations */
sigaddset(&blocked, SIGUSR1); /* reopening log files, sometimes */
sigaddset(&blocked, SIGUSR2); /* who knows what people do */
sigprocmask(SIG_UNBLOCK, &blocked, &saved);
if (ld_env && strstr(ld_env, "libpseudo")) {
pseudo_debug(PDBGF_SERVER, "[server %d] can't run daemon with libpseudo in %s\n", getpid(), PRELINK_LIBRARIES);
s = pseudo_get_value("PSEUDO_UNLOAD");
if (s) {
pseudo_diag("pseudo: I can't seem to make %s go away. Sorry.\n", PRELINK_LIBRARIES);
pseudo_diag("pseudo: %s: %s\n", PRELINK_LIBRARIES, ld_env);
exit(PSEUDO_EXIT_PSEUDO_LOADED);
}
free(s);
pseudo_set_value("PSEUDO_UNLOAD", "YES");
pseudo_setupenv();
pseudo_dropenv(); /* Drop PRELINK_LIBRARIES */
execv(argv[0], argv);
exit(PSEUDO_EXIT_PSEUDO_LOADED);
}
/* Be sure to clean PSEUDO_UNLOAD so if we're asked to run any
* programs pseudo will be active in the process...
* (note: pseudo_set_value doesn't muck w/ the environment, thus
* the need for the unsetenv, which is safe because "pseudo"
* is the executable in this case!)
*/
pseudo_set_value("PSEUDO_UNLOAD", NULL);
unsetenv("PSEUDO_UNLOAD");
/* we need cwd to canonicalize paths */
pseudo_client_getcwd();
/* warning: GNU getopt permutes arguments, which is just plain
* wrong. The + suppresses this annoying behavior, but may not
* be compatible with sane option libraries.
*/
while ((o = getopt(argc, argv, "+BCdfhi:lm:M:p:P:r:R:St:vVx:")) != -1) {
switch (o) {
case 'B': /* rebuild database */
opt_B = 1;
opt_C = 1;
break;
case 'C': /* check database */
opt_C = 1;
break;
case 'd': /* run as daemon */
opt_d = 1;
break;
case 'f': /* run foregrounded */
opt_f = 1;
break;
case 'h': /* help */
usage(0);
break;
case 'i': /* renumber devices, assuming stable inodes */
s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0);
if (!s) {
pseudo_diag("Can't resolve path '%s'\n", optarg);
usage(EXIT_FAILURE);
}
opt_i = strdup(s);
break;
case 'l': /* log */
optptr += snprintf(optptr, pseudo_path_max() - (optptr - opts),
"%s-l", optptr > opts ? " " : "");
opt_l = 1;
break;
case 'm': /* move from... (see also 'M') */
s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0);
if (!s) {
pseudo_diag("Can't resolve move-from path '%s'\n", optarg);
usage(EXIT_FAILURE);
}
opt_m = strdup(s);
break;
case 'M': /* move to... (see also 'm') */
s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0);
if (!s) {
pseudo_diag("Can't resolve move-to path '%s'\n", optarg);
usage(EXIT_FAILURE);
}
opt_M = strdup(s);
break;
case 'p': /* passwd file path */
s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW);
if (!s) {
pseudo_diag("Can't resolve passwd path '%s'\n", optarg);
usage(EXIT_FAILURE);
}
pseudo_set_value("PSEUDO_PASSWD", s);
break;
case 'P': /* prefix */
s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW);
if (!s) {
pseudo_diag("Can't resolve prefix path '%s'\n", optarg);
usage(EXIT_FAILURE);
}
pseudo_set_value("PSEUDO_PREFIX", s);
break;
case 'r': /* chroot to... (fallthrough) */
case 'R': /* pseudo root path */
s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW);
if (!s) {
pseudo_diag("Can't resolve root path '%s'\n", optarg);
usage(EXIT_FAILURE);
}
pseudo_set_value("PSEUDO_CHROOT", s);
if (o == 'r')
opt_r = strdup(s);
break;
case 'S': /* stop */
opt_S = 1;
break;
case 't': /* timeout */
pseudo_server_timeout = strtol(optarg, &s, 10);
if (*s && !isspace(*s)) {
pseudo_diag("Timeout must be an integer value.\n");
usage(EXIT_FAILURE);
}
optptr += snprintf(optptr, pseudo_path_max() - (optptr - opts),
"%s-t %d", optptr > opts ? " " : "",
pseudo_server_timeout);
break;
case 'v': /* verbosity */
pseudo_debug_verbose();
break;
case 'V': /* version info */
printf("pseudo version %s\n", pseudo_version ? pseudo_version : "<undefined>");
printf("pseudo configuration:\n prefix: %s\n",
PSEUDO_PREFIX);
printf("Set PSEUDO_PREFIX to run with a different prefix.\n");
exit(0);
break;
case 'x': /* debug flags */
pseudo_debug_set(optarg);
break;
case '?':
default:
pseudo_diag("unknown/invalid argument (option '%c').\n", optopt);
usage(EXIT_FAILURE);
break;
}
}
pseudo_debug_flags_finalize();
/* Options are processed, preserve them... */
pseudo_set_value("PSEUDO_OPTS", opts);
if (!pseudo_get_prefix(argv[0])) {
pseudo_diag("Can't figure out prefix. Set PSEUDO_PREFIX or invoke with full path.\n");
exit(PSEUDO_EXIT_PSEUDO_PREFIX);
}
/* move database */
if (opt_m || opt_M) {
struct stat buf;
pseudo_msg_t *msg;
int rc;
if (!(opt_m && opt_M)) {
pseudo_diag("You cannot move the database without specifying from and to.\n");
exit(EXIT_FAILURE);
}
if (stat(opt_M, &buf) < 0) {
pseudo_diag("stat of '%s' failed: %s\n",
opt_M, strerror(errno));
pseudo_diag("The directory the database is being moved to must exist.\n");
exit(EXIT_FAILURE);
}
msg = pseudo_msg_new(0, opt_M);
if (!msg) {
pseudo_diag("Can't allocate message structure.\n");
exit(EXIT_FAILURE);
}
rc = pdb_rename_file(opt_m, msg);
free(msg);
if (rc < 0) {
pseudo_diag("Warning: Database move may have failed.\n");
pseudo_diag("To try to restore, you can reverse the move.\n");
pseudo_diag("To commit to this anyway, run pseudo -C to check the database.\n");
exit(EXIT_FAILURE);
}
pseudo_diag("Rename looked okay, running database sanity check.\n");
opt_C = 1;
}
if (opt_i) {
int rc;
struct stat buf;
pseudo_msg_t *msg;
if (stat(opt_i, &buf) < 0) {
pseudo_diag("stat of '%s' failed: %s\n",
opt_i, strerror(errno));
pseudo_diag("The file used to renumber the database must exist.\n");
exit(EXIT_FAILURE);
}
msg = pseudo_msg_new(0, opt_i);
if (!msg) {
pseudo_diag("Couldn't allocate data structure for path.\n");
exit(EXIT_FAILURE);
}
if (pdb_find_file_path(msg)) {
pseudo_diag("Couldn't find a database entry for '%s'.\n", opt_i);
exit(EXIT_FAILURE);
}
if (buf.st_ino != msg->ino) {
pseudo_diag("The database inode entry for '%s' doesn't match; you must use -b.\n",
opt_i);
exit(EXIT_FAILURE);
}
rc = pdb_renumber_all(msg->dev, buf.st_dev);
free(msg);
if (rc < 0) {
pseudo_diag("Warning: Database renumber failed.\n");
exit(EXIT_FAILURE);
}
pseudo_diag("Renumber looked okay, running database sanity check.\n");
opt_C = 1;
}
if (opt_C) {
/* if opt_B is set, try to fix database */
return pseudo_db_check(opt_B);
}
/* If you didn't specify a command, opt_S shuts down here. */
if (opt_S && argc <= optind) {
return pseudo_client_shutdown(0);
}
if (opt_d && opt_f) {
pseudo_diag("You cannot run a foregrounded daemon.\n");
exit(PSEUDO_EXIT_PSEUDO_INVOCATION);
}
if (opt_f || opt_d) {
if (argc > optind) {
pseudo_diag("pseudo: running program implies spawning background daemon.\n");
exit(PSEUDO_EXIT_PSEUDO_INVOCATION);
}
} else {
char fullpath[pseudo_path_max()];
char *path;
if (opt_r) {
if (chdir(opt_r) == -1) {
pseudo_diag("failed to chdir to '%s': %s\n",
opt_r, strerror(errno));
exit(EXIT_FAILURE);
}
}
if (argc > optind) {
pseudo_debug(PDBGF_INVOKE, "running command: %s\n",
argv[optind]);
argc -= optind;
argv += optind;
} else {
static char *newargv[2];
argv = newargv;
pseudo_debug(PDBGF_INVOKE, "running shell.\n");
argv[0] = getenv("SHELL");
if (!argv[0])
argv[0] = "/bin/sh";
argv[1] = NULL;
}
if (strchr(argv[0], '/')) {
snprintf(fullpath, pseudo_path_max(), "%s", argv[0]);
} else {
int found = 0;
if ((path = getenv("PATH")) == NULL)
path = "/bin:/usr/bin";
while (*path) {
struct stat buf;
int len = strcspn(path, ":");
snprintf(fullpath, pseudo_path_max(), "%.*s/%s",
len, path, argv[0]);
path += len;
if (*path == ':')
++path;
if (!stat(fullpath, &buf)) {
if (buf.st_mode & 0111) {
found = 1;
break;
}
}
}
if (!found) {
pseudo_diag("Can't find '%s' in $PATH.\n",
argv[0]);
exit(EXIT_FAILURE);
}
}
pseudo_setupenv();
rc = fork();
if (rc) {
waitpid(rc, &rc, 0);
/* try to hint that we don't think we still need
* the server.
*/
if (opt_S) {
pseudo_client_shutdown(1);
}
if (WIFEXITED(rc)) {
return WEXITSTATUS(rc);
} else if (WIFSIGNALED(rc)) {
kill(getpid(), WTERMSIG(rc));
exit(1);
} else {
exit(1);
}
} else {
rc = execv(fullpath, argv);
if (rc == -1) {
pseudo_diag("pseudo: can't run %s: %s\n",
argv[0], strerror(errno));
}
exit(EXIT_FAILURE);
}
}
return pseudo_server_start(opt_d);
}
/*
* actually process operations.
* This first evaluates the message, figures out what's in the DB, does some
* sanity checks, then implements the fairly small DB changes required.
*/
int
pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag, char **response_path, size_t *response_len) {
pseudo_msg_t msg_header;
pseudo_msg_t by_path = { .op = 0 }, by_ino = { .op = 0 };
pseudo_msg_t db_header;
char *path_by_ino = 0;
char *oldpath = 0;
size_t oldpathlen = 0;
int found_path = 0, found_ino = 0, found_exact = 0;
int prefer_ino = 0;
int xattr_flags = 0;
int trailing_slash = 0;
if (!msg)
return 1;
msg->result = RESULT_SUCCEED;
/* debugging message. Primary key first. */
switch (msg->op) {
case OP_FCHOWN: /* FALLTHROUGH */
case OP_FCHMOD: /* FALLTHROUGH */
case OP_FSTAT:
prefer_ino = 1;
pseudo_debug(PDBGF_OP, "%s %llu [%s]: ", pseudo_op_name(msg->op),
(unsigned long long) msg->ino,
msg->pathlen ? msg->path : "no path");
break;
default:
pseudo_debug(PDBGF_OP, "%s %s [%llu]: ", pseudo_op_name(msg->op),
msg->pathlen ? msg->path : "no path",
(unsigned long long) msg->ino);
break;
}
/* Process rename path separation, there are two paths old / new
* stuff into a rename, break them apart (null seperated)
*/
if (msg->pathlen) {
size_t initial_len;
switch (msg->op) {
case OP_RENAME:
case OP_CREATE_XATTR:
case OP_GET_XATTR:
case OP_LIST_XATTR:
case OP_REPLACE_XATTR:
case OP_REMOVE_XATTR:
case OP_SET_XATTR:
/* In a rename there are two paths, null separated in msg->path */
initial_len = strlen(msg->path);
oldpath = msg->path + initial_len + 1;
/* for rename, the path name would be null-terminated,
* but for *xattr, we don't want the null. */
oldpathlen = msg->pathlen - (oldpath - msg->path) - 1;
pseudo_debug(PDBGF_OP | PDBGF_FILE | PDBGF_XATTR, "%s: path '%s', oldpath '%s' [%d/%d]\n",
pseudo_op_name(msg->op), msg->path, oldpath, (int) oldpathlen, (int) msg->pathlen);
/* For a rename op, we want to strip any trailing
* slashes. For xattr, "oldpath" is the raw data
* to be stored. */
if (oldpathlen > 0 && msg->op == OP_RENAME) {
if (oldpath[oldpathlen - 1] == '/') {
oldpath[--oldpathlen] = '\0';
}
}
/* if we got an oldpath, but a 0-length initial
* path, we don't want to act as though we had
* a non-empty initial path.
*/
msg->pathlen = initial_len;
break;
default:
break;
}
}
/* stash original header, in case we need it later */
msg_header = *msg;
by_ino = msg_header;
/* trailing slashes are kept in paths because they affect
* path resolution, but we don't want them in the database
* because they're optional. For now, any error-checking on
* this server-side is purely advisory, but the client should
* bail with ENOTDIR a lot earlier in many cases, before the
* server even sees anything.
*/
if (msg->pathlen) {
if (msg->path[msg->pathlen - 1] == '/') {
msg->path[--msg->pathlen] = '\0';
trailing_slash = 1;
}
}
/* There should usually be a path. Even for f* ops, the client
* tries to provide a path from its table of known fd paths.
*/
/* Lookup the full path, with inode and dev if available */
if (msg->pathlen && msg->dev && msg->ino) {
if (!pdb_find_file_exact(msg)) {
/* restore header contents */
by_path = *msg;
by_ino = *msg;
*msg = msg_header;
found_path = 1;
found_ino = 1;
found_exact = 1;
/* note: we have to avoid freeing this later */
path_by_ino = msg->path;
if (msg->op == OP_LINK) {
pseudo_debug(PDBGF_FILE, "[matches existing link]");
}
}
}
if (!found_path && !found_ino) {
if (msg->pathlen) {
/* for now, don't canonicalize paths anymore */
/* used to do it here, but now doing it in client */
if (!pdb_find_file_path(msg)) {
by_path = *msg;
found_path = 1;
} else {
if (msg->op != OP_RENAME && msg->op != OP_LINK) {
pseudo_debug(PDBGF_FILE, "(new?) ");
}
}
}
/* search on original inode -- in case of mismatch */
if (msg->dev && msg->ino) {
if (!pdb_find_file_dev(&by_ino, &path_by_ino)) {
found_ino = 1;
}
}
}
pseudo_debug(PDBGF_OP, "incoming: '%s'%s [%llu]%s\n",
msg->pathlen ? msg->path : "no path",
found_path ? "+" : "-",
(unsigned long long) msg_header.ino,
found_ino ? "+" : "-");
/* the sanity checks are inappropriate for DID_UNLINK, since it's
* completely legitimate to have a new database entry for the
* same inode.
*/
if (found_path && msg->op != OP_DID_UNLINK) {
/* This is a bad sign. We should never have a different entry
* for the inode... But an inode of 0 from an EXEC is normal,
* we don't track those.
*/
if (by_path.ino != msg_header.ino && msg_header.ino != 0) {
switch (msg->op) {
case OP_EXEC:
break;
default:
/* if the path is in the database with a
* different inode, but we were expecting
* it to get deleted, mark the old one
* as deleted.
*/
if (by_path.deleting != 0) {
pseudo_debug(PDBGF_FILE, "inode mismatch for '%s' -- old one was marked for deletion, deleting.\n",
msg->path);
/* in this case, we don't trust the
* existing entries, so we will do the
* more expensive sweep for stray
* xattrs.
*/
pdb_did_unlink_file(msg->path, NULL, by_path.deleting);
} else {
pseudo_diag("inode mismatch: '%s' ino %llu in db, %llu in request.\n",
msg->path,
(unsigned long long) by_path.ino,
(unsigned long long) msg_header.ino);
}
}
}
/* If the database entry disagrees on S_ISDIR, it's just
* plain wrong. We remove the database entry, because it
* is absolutely certain to be wrong. This means found_path
* is now 0, because there is no entry in db...
*
* This used to unlink everything with the inode from
* the message -- but what if the entry in the database
* had a different inode? We should nuke THAT inode,
* and everything agreeing with it, which will also catch
* the bogus entry that we noticed.
*/
if (S_ISDIR(by_path.mode) != S_ISDIR(msg_header.mode)) {
pseudo_diag("dir mismatch: '%s' [%llu] db mode 0%o, header mode 0%o (unlinking db)\n",
msg->path, (unsigned long long) by_path.ino,
(int) by_path.mode, (int) msg_header.mode);
/* unlink this path -- the inode may be in use elsewhere */
pdb_unlink_file(msg);
found_path = 0;
} else if (S_ISLNK(by_path.mode) != S_ISLNK(msg_header.mode)) {
pseudo_diag("symlink mismatch: '%s' [%llu] db mode 0%o, header mode 0%o (unlinking db)\n",
msg->path, (unsigned long long) by_path.ino,
(int) by_path.mode, (int) msg_header.mode);
/* unlink this path -- the inode may be in use elsewhere */
pdb_unlink_file(msg);
found_path = 0;
}
if (trailing_slash && !S_ISDIR(by_path.mode)) {
pseudo_diag("dir quasi-mismatch: '%s' [%llu] db mode 0%o, incoming path had trailing slash. Not unlinking.\n",
msg->path, (unsigned long long) by_path.ino,
(int) by_path.mode);
}
}
/* for OP_DID_UNLINK, the reason this op exists is that the same
* inode might have been reclaimed. Don't sanity-check it, and
* especially don't delete the database contents!
*/
if (found_ino && msg->op != OP_DID_UNLINK) {
/* Not always an absolute failure case.
* If a file descriptor shows up unexpectedly and gets
* fchown()d, you could have an entry giving the inode and
* data, but not path. So, we add the path to the entry.
* Any other changes from the incoming message will be applied
* at leisure.
*/
if (msg->pathlen && !path_by_ino) {
pseudo_debug(PDBGF_FILE, "db path missing: ino %llu, request '%s'.\n",
(unsigned long long) msg_header.ino, msg->path);
pdb_update_file_path(msg);
} else if (!msg->pathlen && path_by_ino) {
/* harmless */
pseudo_debug(PDBGF_FILE, "req path missing: ino %llu, db '%s'.\n",
(unsigned long long) msg_header.ino, path_by_ino);
} else if (msg->pathlen && path_by_ino) {
/* this suggests a database error, except in LINK
* cases. In those cases, it is normal for a
* mismatch to occur. :) (SYMLINK shouldn't,
* because the symlink gets its own inode number.)
*
* RENAME can get false positives on this, when
* link count is greater than one. So we skip this
* test for OP_LINK (always) and OP_RENAME (for link
* count greater than one). For RENAME, the test
* should be against the old name, though!
*/
int mismatch = 0;
switch (msg->op) {
case OP_LINK:
case OP_EXEC:
break;
case OP_RENAME:
if (msg->nlink == 1 && strcmp(oldpath, path_by_ino)) {
mismatch = 1;
}
break;
default:
if (strcmp(msg->path, path_by_ino)) {
mismatch = 1;
}
break;
}
if (mismatch) {
/* a mismatch, but we were planning to delete
* the file, so it must have gotten deleted
* already.
*/
if (by_ino.deleting != 0) {
pseudo_debug(PDBGF_FILE, "inode mismatch for '%s' -- old one was marked for deletion, deleting.\n",
msg->path);
pdb_did_unlink_file(path_by_ino, &by_ino, by_ino.deleting);
} else {
int flags = 0;
if (msg->nlink > 1) {
flags = PDBGF_FILE | PDBGF_VERBOSE;
}
pseudo_debug(flags, "path mismatch [%d link%s]: ino %llu db '%s' req '%s'.\n",
msg->nlink,
msg->nlink == 1 ? "" : "s",
(unsigned long long) msg_header.ino,
path_by_ino ? path_by_ino : "no path",
msg->path);
}
}
} else {
/* I don't think I've ever seen this one. */
pseudo_debug(PDBGF_FILE, "warning: ino %llu in db (mode 0%o, owner %d), no path known.\n",
(unsigned long long) msg_header.ino,
(int) by_ino.mode, (int) by_ino.uid);
}
/* Again, in the case of a directory mismatch, nuke the DB
* entry. There is no way it can be right.
*/
if (S_ISDIR(by_ino.mode) != S_ISDIR(msg_header.mode)) {
pseudo_diag("dir err : %llu ['%s'] (db '%s') db mode 0%o, header mode 0%o (unlinking db)\n",
(unsigned long long) msg_header.ino,
msg->pathlen ? msg->path : "no path",
path_by_ino ? path_by_ino : "no path",
(int) by_ino.mode, (int) msg_header.mode);
pdb_unlink_file_dev(msg);
found_ino = 0;
} else if (S_ISLNK(by_ino.mode) != S_ISLNK(msg_header.mode)) {
/* In the current implementation, only msg_header.mode
* can ever be a symlink; the test is generic as
* insurance against forgetting to fix it in a future
* update. */
pseudo_diag("symlink err : %llu ['%s'] (db '%s') db mode 0%o, header mode 0%o (unlinking db)\n",
(unsigned long long) msg_header.ino,
msg->pathlen ? msg->path : "no path",
path_by_ino ? path_by_ino : "no path",
(int) by_ino.mode, (int) msg_header.mode);
pdb_unlink_file_dev(msg);
found_ino = 0;
}
}
/* In the case of a stat() call, if a mismatch existed, we prefer
* by-inode for fstat, by-path for stat. Nothing else actually uses
* this...
*/
if (found_ino && (prefer_ino || !found_path)) {
db_header = by_ino;
} else if (found_path) {
db_header = by_path;
}
switch (msg->op) {
case OP_CHDIR: /* FALLTHROUGH */
case OP_CLOSE:
/* these messages are handled entirely on the client side,
* as of this writing, but might be logged by accident: */
pseudo_diag("error: op %s sent to server.\n", pseudo_op_name(msg->op));
break;
case OP_EXEC: /* FALLTHROUGH */
case OP_OPEN:
/* nothing to do -- just sent in case we're logging */
break;
case OP_CREAT:
/* implies a new file -- not a link, which would be OP_LINK */
if (found_ino && !found_exact) {
/* CREAT should never be sent if the file existed.
* So, any existing entry is an error. Nuke it.
* ... But only if it wasn't a match on both inode *and*
* path, because if it were, that would be harmless.
*/
pseudo_diag("creat for '%s' replaces existing %llu ['%s'].\n",
msg->pathlen ? msg->path : "no path",
(unsigned long long) by_ino.ino,
path_by_ino ? path_by_ino : "no path");
pdb_unlink_file_dev(&by_ino);
}
if (!found_path) {
pseudo_debug(PDBGF_DB, "linking %s for OP_CREAT\n",
msg->pathlen ? msg->path : "no path");
pdb_link_file(msg);
} else {
/* again, an error, but leaving it alone for now. */
pseudo_diag("creat ignored for existing file '%s'.\n",
msg->pathlen ? msg->path : "no path");
}
break;
case OP_CHMOD: /* FALLTHROUGH */
case OP_FCHMOD:
pseudo_debug(PDBGF_OP, "mode 0%o ", (int) msg->mode);
/* if the inode is known, update it */
if (found_ino) {
/* obtain the existing data, merge with mode */
*msg = by_ino;
msg->mode = (msg_header.mode & 07777) |
(msg->mode & ~07777);
pdb_update_file(msg);
} else if (found_path) {
/* obtain the existing data, merge with mode */
*msg = by_path;
msg->mode = (msg_header.mode & 07777) |
(by_path.mode & ~07777);
pdb_update_file(msg);
} else {
/* just in case find_file_path screwed up the msg */
msg->mode = msg_header.mode;
}
/* if we've never seen the file at all before, link it.
* If we have it in the db by inode, but not by name,
* it got fixed during the sanity checks.
*/
if (!found_path && !found_ino) {
pseudo_debug(PDBGF_FILE, "(new) ");
pseudo_debug(PDBGF_DB, "linking %s for OP_[F]CHMOD\n",
msg->pathlen ? msg->path : "no path");
pdb_link_file(msg);
}
break;
case OP_CHOWN: /* FALLTHROUGH */
case OP_FCHOWN:
pseudo_debug(PDBGF_OP, "owner %d:%d ", (int) msg_header.uid, (int) msg_header.gid);
/* if the inode is known, update it */
if (found_ino) {
/* obtain the existing data, merge with mode */
*msg = by_ino;
msg->uid = msg_header.uid;
msg->gid = msg_header.gid;
pdb_update_file(msg);
} else if (found_path) {
/* obtain the existing data, merge with mode */
*msg = by_path;
msg->uid = msg_header.uid;
msg->gid = msg_header.gid;
pdb_update_file(msg);
} else {
/* just in case find_file_path screwed up the msg */
msg->uid = msg_header.uid;
msg->gid = msg_header.gid;
}
/* if we've never seen the file at all before, link it.
* If we have it in the db by inode, but not by name,
* it got fixed during the sanity checks.
*/
if (!found_path && !found_ino) {
pseudo_debug(PDBGF_FILE, "(new) ");
pseudo_debug(PDBGF_DB, "linking %s for OP_[F]CHOWN\n",
msg->pathlen ? msg->path : "no path");
pdb_link_file(msg);
}
break;
case OP_STAT: /* FALLTHROUGH */
case OP_FSTAT:
/* db_header will be whichever one looked best, in the rare
* case where there might be a clash.
*/
if (found_ino || found_path) {
#ifdef PSEUDO_XATTRDB
if (db_header.uid == (uid_t) -1 &&
db_header.gid == (gid_t) -1) {
/* special case: this row was created
* to allow xattr lookups, and it's not
* actually valid data.
*/
msg->result = RESULT_FAIL;
} else
#endif
{
*msg = db_header;
}
} else {
msg->result = RESULT_FAIL;
}
pseudo_debug(PDBGF_OP | PDBGF_VERBOSE, "%s, ino %llu (old mode 0%o): mode 0%o\n",
pseudo_op_name(msg->op), (unsigned long long) msg->ino,
(int) msg_header.mode, (int) msg->mode);
break;
case OP_LINK: /* FALLTHROUGH */
case OP_SYMLINK:
/* a successful link (client only notifies us for those)
* implies that the new path did not previously exist, and
* the old path did. We get the stat buffer and the new path.
* So, we unlink it, then link it. Neither unlink nor link
* touches the message, which was initialized from the
* underlying file data in the client.
*/
if (found_path) {
pseudo_debug(PDBGF_OP | PDBGF_FILE, "replace %slink: path %s, old ino %llu, mode 0%o, new ino %llu, mode 0%o\n",
msg->op == OP_SYMLINK ? "sym" : "",
msg->path, (unsigned long long) msg->ino,
(int) msg->mode,
(unsigned long long) msg_header.ino,
(int) msg_header.mode);
pdb_unlink_file(msg);
} else {
pseudo_debug(PDBGF_OP | PDBGF_FILE, "new %slink: path %s, ino %llu, mode 0%o\n",
msg->op == OP_SYMLINK ? "sym" : "",
msg->path,
(unsigned long long) msg_header.ino,
(int) msg_header.mode);
}
if (found_ino) {
if (msg->op == OP_SYMLINK) {
pseudo_debug(PDBGF_OP | PDBGF_FILE, "symlink: ignoring existing file %llu ['%s']\n",
(unsigned long long) by_ino.ino,
path_by_ino ? path_by_ino : "no path");
} else {
*msg = by_ino;
pseudo_debug(PDBGF_OP | PDBGF_FILE, "link: copying data from existing file %llu ['%s']\n",
(unsigned long long) by_ino.ino,
path_by_ino ? path_by_ino : "no path");
}
} else {
*msg = msg_header;
}
pseudo_debug(PDBGF_DB, "linking %s for %s\n",
msg->pathlen ? msg->path : "no path",
pseudo_op_name(msg->op));
pdb_link_file(msg);
break;
case OP_RENAME:
/* a rename implies renaming an existing entry... and every
* database entry rooted in it, if it's a directory.
*/
pdb_rename_file(oldpath, msg);
pdb_update_inode(msg);
break;
case OP_MAY_UNLINK:
if (pdb_may_unlink_file(msg, msg->client)) {
/* harmless, but client wants to know so it knows
* whether to follow up... */
msg->result = RESULT_FAIL;
}
break;
case OP_DID_UNLINK:
pdb_did_unlink_file(msg->path, msg, msg->client);
break;
case OP_CANCEL_UNLINK:
pdb_cancel_unlink_file(msg);
break;
case OP_UNLINK:
/* this removes any entries with the given path from the
* database. No response is needed.
* DO NOT try to fail if the entry is already gone -- if the
* server's response didn't make it, the client would resend.
*/
pdb_unlink_file(msg);
pdb_unlink_contents(msg);
/* If we are seeing an unlink for something with only one
* link, we should delete all records for that inode, even
* ones through different paths. This handles the case
* where something is removed through the wrong path, but
* only if it didn't have multiple hard links.
*
* This should cease to be needed once symlinks are tracked.
*/
if (msg_header.nlink == 1 && found_ino) {
pseudo_debug(PDBGF_FILE | PDBGF_OP, "link count 1, unlinking anything with ino %llu.\n",
(unsigned long long) msg->ino);
pdb_unlink_file_dev(msg);
}
msg->result = RESULT_NONE;
break;
case OP_MKDIR: /* FALLTHROUGH */
case OP_MKNOD:
pseudo_debug(PDBGF_OP, "mode 0%o", (int) msg->mode);
/* for us to get called, the client has to have succeeded in
* a creation (of a regular file, for mknod) -- meaning this
* file DID NOT exist before the call. Fix database:
*/
if (found_path) {
pseudo_diag("mkdir/mknod: '%s' [%llu] already existed (mode 0%o), unlinking\n",
msg->path, (unsigned long long) by_path.ino,
(int) by_path.mode);
pdb_unlink_file(msg);
}
if (found_ino) {
pdb_unlink_file_dev(&by_ino);
}
*msg = msg_header;
pseudo_debug(PDBGF_DB, "linking %s for %s\n",
msg->pathlen ? msg->path : "no path",
pseudo_op_name(msg->op));
pdb_link_file(msg);
break;
case OP_GET_XATTR:
if (pdb_get_xattr(msg, &oldpath, &oldpathlen)) {
msg->result = RESULT_FAIL;
} else {
*response_path = oldpath;
*response_len = oldpathlen;
pseudo_debug(PDBGF_XATTR, "get results: '%.*s' (%d bytes)\n",
(int) *response_len, *response_path, (int) *response_len);
}
break;
case OP_LIST_XATTR:
if (pdb_list_xattr(msg, &oldpath, &oldpathlen)) {
msg->result = RESULT_FAIL;
} else {
pseudo_debug(PDBGF_XATTR, "got %d bytes of xattrs to list: %.*s\n", (int) oldpathlen, (int) oldpathlen, oldpath);
*response_path = oldpath;
*response_len = oldpathlen;
}
break;
case OP_CREATE_XATTR:
case OP_REPLACE_XATTR: /* fallthrough */
if (msg->op == OP_CREATE_XATTR) {
xattr_flags = XATTR_CREATE;
}
if (msg->op == OP_REPLACE_XATTR) {
xattr_flags = XATTR_REPLACE;
}
/* fallthrough */
case OP_SET_XATTR:
if (pdb_set_xattr(msg, oldpath, oldpathlen, xattr_flags)) {
msg->result = RESULT_FAIL;
}
break;
case OP_REMOVE_XATTR:
pdb_remove_xattr(msg, oldpath, oldpathlen);
break;
default:
pseudo_diag("unknown op from client %d, op %d [%s]\n",
msg->client, msg->op,
msg->pathlen ? msg->path : "no path");
break;
}
/* in the case of an exact match, we just used the pointer
* rather than allocating space.
*/
if (path_by_ino != msg->path) {
free(path_by_ino);
}
pseudo_debug(PDBGF_OP, "completed %s.\n", pseudo_op_name(msg->op));
if (opt_l)
pdb_log_msg(SEVERITY_INFO, msg, program, tag, NULL);
return 0;
}
/* SHUTDOWN does not get this far, it's handled in pseudo_server.c */
int
pseudo_server_response(pseudo_msg_t *msg, const char *program, const char *tag, char **response_path, size_t *response_len) {
switch (msg->type) {
case PSEUDO_MSG_PING:
/* mad hackery: if we aren't logging, the client gets told
* not to send open/exec notifications, which have no other
* purpose.
*/
msg->result = opt_l ? RESULT_SUCCEED : RESULT_FAIL;
if (opt_l)
pdb_log_msg(SEVERITY_INFO, msg, program, tag, NULL);
return 0;
break;
case PSEUDO_MSG_OP:
case PSEUDO_MSG_FASTOP:
return pseudo_op(msg, program, tag, response_path, response_len);
break;
case PSEUDO_MSG_ACK: /* FALLTHROUGH */
case PSEUDO_MSG_NAK: /* FALLTHROUGH */
default:
pdb_log_msg(SEVERITY_WARN, msg, program, tag, "invalid message");
return 1;
}
}
int
pseudo_db_check(int fix) {
struct stat buf;
pseudo_msg_t *m;
pdb_file_list l;
int errors = 0;
int delete_some = 0;
/* magic cookie used to show who's deleting the files */
int magic_cookie = (int) getpid();
int rc = 0;
l = pdb_files();
if (!l) {
pseudo_diag("Couldn't start file list, can't scan.\n");
return EXIT_FAILURE;
}
while ((m = pdb_file(l)) != NULL) {
pseudo_debug(PDBGF_DB, "m: %p (%d: %s)\n",
(void *) m,
m ? (int) m->pathlen : -1,
m ? m->path : "<n/a>");
if (m->pathlen > 0) {
int fixup_needed = 0;
pseudo_debug(PDBGF_DB, "Checking <%s>\n", m->path);
if (lstat(m->path, &buf)) {
errors = EXIT_FAILURE;
pseudo_diag("can't stat <%s>\n", m->path);
continue;
}
/* can't check for device type mismatches, uid/gid, or
* permissions, because those are the very things we
* can't really set.
*/
if (buf.st_ino != m->ino) {
pseudo_debug(PDBGF_DB, "ino mismatch <%s>: ino %llu, db %llu\n",
m->path,
(unsigned long long) buf.st_ino,
(unsigned long long) m->ino);
m->ino = buf.st_ino;
fixup_needed = 1;
}
if (buf.st_dev != m->dev) {
pseudo_debug(PDBGF_DB, "dev mismatch <%s>: dev %llu, db %llu\n",
m->path,
(unsigned long long) buf.st_dev,
(unsigned long long) m->dev);
m->dev = buf.st_dev;
fixup_needed = 1;
}
if (S_ISLNK(buf.st_mode) != S_ISLNK(m->mode)) {
pseudo_debug(PDBGF_DB, "symlink mismatch <%s>: file %d, db %d\n",
m->path,
S_ISLNK(buf.st_mode),
S_ISLNK(m->mode));
fixup_needed = 2;
}
if (S_ISDIR(buf.st_mode) != S_ISDIR(m->mode)) {
pseudo_debug(PDBGF_DB, "symlink mismatch <%s>: file %d, db %d\n",
m->path,
S_ISDIR(buf.st_mode),
S_ISDIR(m->mode));
fixup_needed = 2;
}
if (fixup_needed) {
/* in fixup mode, either delete (mismatches) or
* correct (dev/ino).
*/
if (fix) {
if (fixup_needed == 1) {
rc = pdb_update_inode(m);
} else if (fixup_needed == 2) {
/* mark for deletion */
delete_some = 1;
rc = pdb_may_unlink_file(m, magic_cookie);
}
if (rc) {
pseudo_diag("error updating file %s\n",
m->path);
errors = EXIT_FAILURE;
}
} else {
errors = EXIT_FAILURE;
}
}
}
}
pdb_files_done(l);
/* and now delete files marked for deletion */
if (delete_some) {
rc = pdb_did_unlink_files(magic_cookie);
if (rc) {
pseudo_diag("error nuking mismatched files.\n");
pseudo_diag("database may not be fixed.\n");
errors = EXIT_FAILURE;
}
}
return errors;
}