/* * pseudo.c, main pseudo utility program * * Copyright (c) 2008-2013 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 : ""); 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(); } 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(); } 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; 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; /* 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 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 (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 { 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(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) { /* 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) 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; } 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 : ""); 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; }