diff --git a/src/apps/manager/manager.c b/src/apps/manager/manager.c index 7f4fb67..cf754b3 100644 --- a/src/apps/manager/manager.c +++ b/src/apps/manager/manager.c @@ -12,6 +12,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -57,6 +62,67 @@ static BOOL Exiting = FALSE; static BOOL ChildError = FALSE; static BOOL ReloadNow = FALSE; static int HighestPriority = 0; +static int LockFD = -1; +static pid_t LockOwnerPID = -1; + +static pid_t ReadPid(const char *filename); +static BOOL Unlock(const char *lockfile); + +static void +CleanupManagerLock(void) +{ + if (LockFD != -1) { + Unlock(LOCKFILE); + } +} + +/* + * PID files alone are not locks: after an unclean shutdown the PID can be + * reused by an unrelated process. Keep an advisory write lock for the life + * of the manager and use /proc, where available, to recognise pid files left + * by older Bongo versions which did not take that lock. + */ +static BOOL +ManagerProcessIsRunning(pid_t pid) +{ + if (pid <= 1) { + return FALSE; + } + +#ifdef __linux__ + { + char path[64]; + char name[64]; + FILE *comm; + + int length = snprintf(path, sizeof(path), "/proc/%ld/comm", (long)pid); + + if (length >= 0 && length < (int)sizeof(path)) { + comm = fopen(path, "r"); + if (comm != NULL) { + BOOL matches = FALSE; + + if (fgets(name, sizeof(name), comm) != NULL) { + name[strcspn(name, "\r\n")] = '\0'; + matches = (strcmp(name, "bongo-manager") == 0); + } + fclose(comm); + return matches; + } + + if (errno == ENOENT) { + return FALSE; + } + } + } +#endif + + if (kill(pid, 0) == 0) { + return TRUE; + } + + return errno == EPERM; +} static BongoAgent * FindAgentByPid(pid_t pid) @@ -302,9 +368,9 @@ StartAgentsWithPriority(int priority, BOOL onlyCrashed, BOOL printMessage) static BOOL LoadAgentConfiguration() { - char *config; + char *config = NULL; BOOL retcode = FALSE; - BongoJsonNode *node; + BongoJsonNode *node = NULL; BongoJsonResult res; GArray *agentlist; unsigned int i; @@ -345,7 +411,8 @@ LoadAgentConfiguration() retcode = TRUE; finish: - BongoJsonNodeFree(node); + if (node) BongoJsonNodeFree(node); + if (config) MemFree(config); return retcode; } @@ -401,50 +468,137 @@ SignalHandler(int signo) static int Lock(BOOL force) { + struct flock lock; + struct stat status; int fd; int remaining; int written; int ret; int mode; pid_t pid; - char pidstr[20]; + pid_t oldPid; + char pidstr[32]; - mode = O_CREAT | O_WRONLY; - if (!force) { - mode |= O_EXCL; - } + mode = O_CREAT | O_RDWR; +#ifdef O_CLOEXEC + mode |= O_CLOEXEC; +#endif +#ifdef O_NOFOLLOW + mode |= O_NOFOLLOW; +#endif - fd = open(LOCKFILE, mode, S_IREAD | S_IWRITE); + fd = open(LOCKFILE, mode, S_IRUSR | S_IWUSR); if (fd == -1) { return -1; } + if (fstat(fd, &status) == -1) { + int savedErrno = errno; + close(fd); + errno = savedErrno; + return -1; + } + if (!S_ISREG(status.st_mode)) { + close(fd); + errno = EINVAL; + return -1; + } + if (fchmod(fd, S_IRUSR | S_IWUSR) == -1) { + int savedErrno = errno; + close(fd); + errno = savedErrno; + return -1; + } + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) { + int savedErrno = errno; + close(fd); + errno = savedErrno; + return -1; + } + + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + if (fcntl(fd, F_SETLK, &lock) == -1) { + int savedErrno = errno; + close(fd); + errno = (savedErrno == EACCES || savedErrno == EAGAIN) ? + EEXIST : savedErrno; + return -1; + } + + { + char oldPidString[32]; + ssize_t count; + char *end; + long value; + + oldPid = -1; + count = pread(fd, oldPidString, sizeof(oldPidString) - 1, 0); + if (count > 0) { + oldPidString[count] = '\0'; + errno = 0; + value = strtol(oldPidString, &end, 10); + if (errno == 0 && end != oldPidString && value > 1 && + (*end == '\n' || *end == '\0')) { + oldPid = (pid_t)value; + if ((long)oldPid != value) { + oldPid = -1; + } + } + } + } + if (!force && oldPid != getpid() && ManagerProcessIsRunning(oldPid)) { + close(fd); + errno = EEXIST; + return -1; + } + + if (ftruncate(fd, 0) == -1 || lseek(fd, 0, SEEK_SET) == (off_t)-1) { + int savedErrno = errno; + close(fd); + errno = savedErrno; + return -1; + } + pid = getpid(); - if ((unsigned long)pid > 999999) { - /* pid < 0 || pid > pidmax ceiling on Solaris */ + if (pid <= 1) { fprintf(stderr, _("bongo-manager: unlikely pid, Exiting\n")); + close(fd); exit(1); } remaining = snprintf(pidstr, sizeof(pidstr), "%ld\n", (long int)pid); + if (remaining < 0 || remaining >= (int)sizeof(pidstr)) { + close(fd); + errno = EOVERFLOW; + return -1; + } ret = 0; written = 0; while (remaining > 0) { ret = write(fd, pidstr + written, remaining); if (ret != -1) { + if (ret == 0) { + close(fd); + errno = EIO; + return -1; + } remaining -= ret; written += ret; } else if (errno == EINTR) { continue; } else { + int savedErrno = errno; + close(fd); + errno = savedErrno; return ret; } } - if (close(fd) == -1) { - return -1; - } + LockFD = fd; + LockOwnerPID = pid; return ret; } @@ -453,26 +607,56 @@ Lock(BOOL force) static BOOL Unlock(const char *lockfile) { - if (unlink(lockfile) == -1) { - fprintf(stderr, _("bongo-manager: couldn't unlink %s\n"), LOCKFILE); - return FALSE; + BOOL result = TRUE; + + if (LockOwnerPID != getpid()) { + if (LockFD != -1) { + close(LockFD); + LockFD = -1; + } + return TRUE; } - - return TRUE; + + if (unlink(lockfile) == -1) { + if (errno != ENOENT) { + fprintf(stderr, _("bongo-manager: couldn't unlink %s\n"), LOCKFILE); + result = FALSE; + } + } + + if (LockFD != -1) { + if (close(LockFD) == -1) { + result = FALSE; + } + LockFD = -1; + } + LockOwnerPID = -1; + + return result; } static pid_t ReadPid(const char *filename) { FILE *f; - char pidstr[20]; + char pidstr[32]; + char *end; + long value; pid_t ret = -1; f = fopen(filename, "r"); if (f) { if (fgets(pidstr, sizeof(pidstr), f)) { - ret = (pid_t)strtol(pidstr, NULL, 10); + errno = 0; + value = strtol(pidstr, &end, 10); + if (errno == 0 && end != pidstr && value > 1 && + (*end == '\n' || *end == '\0')) { + ret = (pid_t)value; + if ((long)ret != value) { + ret = -1; + } + } } fclose(f); } else { @@ -650,7 +834,7 @@ main(int argc, char **argv) if (reload) { pid_t pid = ReadPid(LOCKFILE); - if (pid <= 0) { + if (pid <= 0 || !ManagerProcessIsRunning(pid)) { fprintf(stderr, _("bongo-manager: could not open pid file '%s'\n"), LOCKFILE); exit(1); } @@ -668,7 +852,7 @@ main(int argc, char **argv) if (shutdown) { pid_t pid = ReadPid(LOCKFILE); - if (pid <= 0) { + if (pid <= 0 || !ManagerProcessIsRunning(pid)) { fprintf(stderr, _("bongo-manager: could not open pid file '%s'\n"), LOCKFILE); exit(1); } @@ -690,20 +874,10 @@ main(int argc, char **argv) exit(1); } -get_lock: if (Lock(force) == -1) { if (errno == EEXIST) { - pid_t pid = ReadPid(LOCKFILE); - if (kill(pid, 0) != -1 || errno != ESRCH) { - fprintf(stderr, _("bongo-manager: another bongo-manager process appears to be running.\n")); - fprintf(stderr, _("bongo-manager: run with -s to stop an existing process, or -f to ignore the existing pidfile.\n")); - } else if (!force) { - fprintf(stderr, _("bongo-manager: removing stale lock file in %s.\n"), LOCKFILE); - force = TRUE; - goto get_lock; - } else { - fprintf (stderr, _("bongo-manager: could not remove stale lock file in %s.\n"), LOCKFILE); - } + fprintf(stderr, _("bongo-manager: another bongo-manager process appears to be running.\n")); + fprintf(stderr, _("bongo-manager: run with -s to stop an existing process, or -f to ignore the existing pidfile.\n")); } else if (errno == EPERM) { fprintf(stderr, _("bongo-manager: %s user does not have permission to create a lock file in %s\n"), MsgGetUnprivilegedUser(), XPL_DEFAULT_WORK_DIR); @@ -714,6 +888,10 @@ get_lock: goto err_handler; } unlockFile = TRUE; + if (atexit(CleanupManagerLock) != 0) { + fprintf(stderr, _("bongo-manager: could not register pid file cleanup\n")); + goto err_handler; + } ConnStartup(DEFAULT_CONNECTION_TIMEOUT); @@ -729,7 +907,7 @@ get_lock: StartStore(); if (!LoadAgentConfiguration()) { printf(_("bongo-manager: Couldn't load configuration for agents\n")); - exit(-1); // no point continuing... + goto err_handler; } StartAgents(FALSE, FALSE);