aboutsummaryrefslogtreecommitdiffstats
path: root/pseudo.c
diff options
context:
space:
mode:
Diffstat (limited to 'pseudo.c')
-rw-r--r--pseudo.c667
1 files changed, 667 insertions, 0 deletions
diff --git a/pseudo.c b/pseudo.c
new file mode 100644
index 0000000..515f447
--- /dev/null
+++ b/pseudo.c
@@ -0,0 +1,667 @@
+/*
+ * pseudo.c, main pseudo utility program
+ *
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdlib.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 "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_client.h"
+#include "pseudo_server.h"
+#include "pseudo_db.h"
+
+int opt_d = 0;
+int opt_f = 0;
+int opt_l = 0;
+long opt_p = 0;
+int opt_S = 0;
+
+static int pseudo_op(pseudo_msg_t *msg, const char *tag);
+
+void
+usage(void) {
+ pseudo_diag("Usage: pseudo [-dflv] [-P prefix] [-t timeout] [command]\n");
+ pseudo_diag(" pseudo [-dflv] [-P prefix] -S\n");
+ pseudo_diag(" pseudo [-dflv] [-P prefix] -V\n");
+ exit(1);
+}
+
+/* main server process */
+int
+main(int argc, char *argv[]) {
+ int o;
+ char *s;
+ int lockfd, newfd;
+ char *ld_env = getenv("LD_PRELOAD");
+ int rc;
+ char opts[PATH_MAX] = "", *optptr = opts;
+ char *lockname;
+
+ s = getenv("PSEUDO_DEBUG");
+ if (s) {
+ int level = atoi(s);
+ for (o = 0; o < level; ++o) {
+ pseudo_debug_verbose();
+ }
+ }
+
+ if (ld_env && strstr(ld_env, "libpseudo")) {
+ extern char **environ;
+
+ pseudo_debug(2, "can't run daemon with libpseudo in LD_PRELOAD\n");
+ if (getenv("PSEUDO_RELOADED")) {
+ pseudo_diag("I can't seem to make LD_PRELOAD go away. Sorry.\n");
+ exit(1);
+ }
+ setenv("PSEUDO_RELOADED", "YES", 1);
+ pseudo_dropenv();
+ execve(argv[0], argv, environ);
+ exit(1);
+ }
+ unsetenv("PSEUDO_RELOADED");
+
+ /* 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, "+dflP:St:vV")) != -1) {
+ switch (o) {
+ case 'd':
+ /* run as daemon */
+ opt_d = 1;
+ break;
+ case 'f':
+ /* run foregrounded */
+ opt_f = 1;
+ break;
+ case 'l':
+ optptr += snprintf(optptr, PATH_MAX - (optptr - opts),
+ "%s-l", optptr > opts ? " " : "");
+ opt_l = 1;
+ break;
+ case 'P':
+ setenv("PSEUDO_PREFIX", optarg, 1);
+ break;
+ case 'S':
+ opt_S = 1;
+ break;
+ case 't':
+ pseudo_server_timeout = strtol(optarg, &s, 10);
+ if (*s && !isspace(*s)) {
+ pseudo_diag("Timeout must be an integer value.\n");
+ usage();
+ }
+ optptr += snprintf(optptr, PATH_MAX - (optptr - opts),
+ "%s-t %d", optptr > opts ? " " : "",
+ pseudo_server_timeout);
+ break;
+ case 'v':
+ pseudo_debug_verbose();
+ break;
+ case 'V':
+ printf("pseudo version %s\n", pseudo_version ? pseudo_version : "<undefined>");
+ printf("pseudo configuration flags:\n prefix: %s\n suffix: %s\n",
+ PSEUDO_PREFIX,
+ PSEUDO_SUFFIX);
+ printf("Set PSEUDO_PREFIX to run with a different prefix.\n");
+ exit(0);
+ break;
+ case '?':
+ pseudo_diag("unknown/invalid argument (option '%c').\n", optopt);
+ usage();
+ break;
+ }
+ }
+
+ if (!pseudo_get_prefix(argv[0])) {
+ pseudo_diag("Can't figure out prefix. Set PSEUDO_PREFIX or invoke with full path.\n");
+ exit(1);
+ }
+
+ if (opt_S) {
+ return pseudo_client_shutdown();
+ }
+
+ if (opt_d && opt_f) {
+ pseudo_diag("You cannot run a foregrounded daemon.\n");
+ exit(1);
+ }
+
+ if (opt_f || opt_d) {
+ if (argc > optind) {
+ pseudo_diag("pseudo: running program implies spawning background daemon.\n");
+ exit(1);
+ }
+ } else {
+ if (argc > optind) {
+ pseudo_debug(2, "running command: %s\n",
+ argv[optind]);
+ argc -= optind;
+ argv += optind;
+ } else {
+ static char *newargv[2];
+ argv = newargv;
+ pseudo_debug(2, "running shell.\n");
+ argv[0] = getenv("SHELL");
+ if (!argv[0])
+ argv[0] = "/bin/sh";
+ argv[1] = NULL;
+ }
+ /* build an environment containing libpseudo */
+ pseudo_debug(2, "setting up pseudo environment.\n");
+ pseudo_setupenv(opts);
+ rc = execvp(argv[0], argv);
+ if (rc == -1) {
+ pseudo_diag("pseudo: can't run %s: %s\n",
+ argv[0], strerror(errno));
+ }
+ exit(1);
+ }
+ /* if we got here, we are not running a command, and we are not in
+ * a pseudo environment.
+ */
+ pseudo_new_pid();
+
+ pseudo_debug(3, "opening lock.\n");
+ lockname = pseudo_prefix_path(PSEUDO_LOCKFILE);
+ if (!lockname) {
+ pseudo_diag("Couldn't allocate a file path.\n");
+ exit(1);
+ }
+ lockfd = open(lockname, O_RDWR | O_CREAT, 0644);
+ if (lockfd < 0) {
+ pseudo_diag("Can't open or create lockfile %s: %s\n",
+ lockname, strerror(errno));
+ exit(1);
+ }
+ free(lockname);
+
+ if (lockfd <= 2) {
+ newfd = fcntl(lockfd, F_DUPFD, 3);
+ if (newfd < 0) {
+ pseudo_diag("Can't move lockfile to safe descriptor: %s\n",
+ strerror(errno));
+ } else {
+ close(lockfd);
+ lockfd = newfd;
+ }
+ }
+
+ pseudo_debug(3, "acquiring lock.\n");
+ if (flock(lockfd, LOCK_EX | LOCK_NB) < 0) {
+ if (errno == EACCES || errno == EAGAIN) {
+ pseudo_debug(1, "Existing server has lock. Exiting.\n");
+ } else {
+ pseudo_diag("Error obtaining lock: %s\n", strerror(errno));
+ }
+ exit(0);
+ } else {
+ pseudo_debug(2, "Acquired lock.\n");
+ }
+ pseudo_debug(3, "serving (%s)\n", opt_d ? "daemon" : "foreground");
+ 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 *tag) {
+ pseudo_msg_t msg_header;
+ pseudo_msg_t by_path = { 0 }, by_ino = { 0 };
+ pseudo_msg_t db_header;
+ char *path_by_ino = 0;
+ char *oldpath = 0;
+ int found_path = 0, found_ino = 0;
+ int prefer_ino = 0;
+
+ if (!msg)
+ return 1;
+
+ msg->result = RESULT_SUCCEED;
+ msg->xerrno = 0;
+
+ /* debugging message. Primary key first. */
+ switch (msg->op) {
+ case OP_FCHOWN:
+ case OP_FCHMOD:
+ case OP_FSTAT:
+ prefer_ino = 1;
+ pseudo_debug(2, "%s %llu [%s]: ", pseudo_op_name(msg->op),
+ (unsigned long long) msg->ino,
+ msg->pathlen ? msg->path : "no path");
+ break;
+ default:
+ pseudo_debug(2, "%s %s [%llu]: ", pseudo_op_name(msg->op),
+ msg->pathlen ? msg->path : "no path",
+ (unsigned long long) msg->ino);
+ break;
+ }
+
+ /* stash original header, in case we need it later */
+ msg_header = *msg;
+
+ /* There should usually be a path. Even for f* ops, the client
+ * tries to provide a path from its table of known fd paths.
+ */
+ if (msg->pathlen) {
+ if (msg->op == OP_RENAME) {
+ oldpath = msg->path + strlen(msg->path) + 1;
+ pseudo_debug(2, "rename: path %s, oldpath %s\n",
+ msg->path, oldpath);
+ }
+ /* 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(3, "(new?) ");
+ }
+ }
+ }
+
+ /* search on original inode -- in case of mismatch */
+ by_ino = msg_header;
+ if (msg_header.ino != 0) {
+ if (msg->pathlen && !pdb_find_file_exact(msg)) {
+ /* restore header contents */
+ by_ino = *msg;
+ *msg = msg_header;
+ found_ino = 1;
+ /* note: we have to avoid freeing this later */
+ path_by_ino = msg->path;
+ } else if (!pdb_find_file_dev(&by_ino)) {
+ found_ino = 1;
+ path_by_ino = pdb_get_file_path(&by_ino);
+ }
+ }
+
+ pseudo_debug(3, "incoming: '%s'%s [%llu]%s\n",
+ msg->pathlen ? msg->path : "no path",
+ found_path ? "+" : "-",
+ (unsigned long long) msg_header.ino,
+ found_ino ? "+" : "-");
+
+ if (found_path) {
+ /* This is a bad sign. We should never have a different entry
+ * for the inode...
+ */
+ if (by_path.ino != msg_header.ino) {
+ 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 everything with this inode */
+ pdb_unlink_file_dev(&by_path);
+ 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 everything with this inode */
+ pdb_unlink_file_dev(&by_path);
+ found_path = 0;
+ }
+ }
+
+ if (found_ino) {
+ /* 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(2, "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(2, "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:
+ 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) {
+ pseudo_diag("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(1, "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:
+ 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_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) {
+ /* CREAT should never be sent if the file existed.
+ * So, any existing entry is an error. Nuke it.
+ */
+ pseudo_diag("creat for '%s' replaces existing %llu ['%s'].\n",
+ msg->pathlen ? msg->path : "no path",
+ (unsigned long long) msg_header.ino,
+ path_by_ino ? path_by_ino : "no path");
+ pdb_unlink_file_dev(&by_ino);
+ }
+ if (!found_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:
+ case OP_FCHMOD:
+ pseudo_debug(2, "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 the path is not known, link it */
+ if (!found_path) {
+ pseudo_debug(2, "(new) ");
+ pdb_link_file(msg);
+ }
+ break;
+ case OP_CHOWN:
+ case OP_FCHOWN:
+ pseudo_debug(2, "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 the path is not known, link it */
+ if (!found_path) {
+ pdb_link_file(msg);
+ }
+ break;
+ case OP_STAT:
+ 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) {
+ *msg = db_header;
+ } else {
+ msg->result = RESULT_FAIL;
+ }
+ pseudo_debug(3, "%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:
+ 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(2, "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(2, "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(2, "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(2, "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;
+ }
+ pdb_link_file(msg);
+ break;
+ case OP_RENAME:
+ /* a rename implies renaming an existing entry... and every
+ * database entry rooted in it.
+ */
+ pdb_rename_file(oldpath, 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);
+ /* 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(2, "unlink, link count 1, unlinking anything with ino %llu.\n",
+ (unsigned long long) msg->ino);
+ pdb_unlink_file_dev(msg);
+ }
+ break;
+ case OP_MKDIR:
+ case OP_MKNOD:
+ pseudo_debug(2, "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;
+ pdb_link_file(msg);
+ 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(2, "completed %s.\n", pseudo_op_name(msg->op));
+ if (opt_l)
+ pdb_log_msg(SEVERITY_INFO, msg, 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 *tag) {
+ switch (msg->type) {
+ case PSEUDO_MSG_PING:
+ msg->result = RESULT_SUCCEED;
+ if (opt_l)
+ pdb_log_msg(SEVERITY_INFO, msg, tag, "ping");
+ return 0;
+ break;
+ case PSEUDO_MSG_OP:
+ return pseudo_op(msg, tag);
+ break;
+ case PSEUDO_MSG_ACK:
+ case PSEUDO_MSG_NAK:
+ default:
+ pdb_log_msg(SEVERITY_WARN, msg, tag, "invalid message");
+ return 1;
+ }
+}