#include <arpa/inet.h>
#include <ifaddrs.h>
-static pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER;
+extern pthread_mutex_t thread_mutex;
lxc_log_define(lxc_container, lxc);
/* LOCKING
- * c->privlock protects the struct lxc_container from multiple threads.
- * c->slock protects the on-disk container data
- * thread_mutex protects process data (ex: fd table) from multiple threads
+ * 1. c->privlock protects the struct lxc_container from multiple threads.
+ * 2. c->slock protects the on-disk container data
+ * 3. thread_mutex protects process data (ex: fd table) from multiple threads
+ * slock is an flock, which does not exclude threads. Therefore slock should
+ * always be wrapped inside privlock.
* NOTHING mutexes two independent programs with their own struct
* lxc_container for the same c->name, between API calls. For instance,
* c->config_read(); c->start(); Between those calls, data on disk
c->error_string = NULL;
}
if (c->slock) {
- sem_close(c->slock);
+ lxc_putlock(c->slock);
c->slock = NULL;
}
if (c->privlock) {
- sem_t *l = c->privlock;
+ lxc_putlock(c->privlock);
c->privlock = NULL;
- sem_destroy(l);
- free(l);
}
if (c->name) {
free(c->name);
if (!c)
return NULL;
- if (lxclock(c->slock, 0))
+ if (lxclock(c->privlock, 0))
return NULL;
+ if (lxclock(c->slock, 0))
+ goto bad;
s = lxc_getstate(c->name, c->config_path);
ret = lxc_state2str(s);
lxcunlock(c->slock);
+bad:
+ lxcunlock(c->privlock);
return ret;
}
if (!c)
return false;
- if (lxclock(c->slock, 0))
+ if (lxclock(c->privlock, 0))
return false;
+ if (lxclock(c->slock, 0)) {
+ lxcunlock(c->privlock);
+ return false;
+ }
ret = lxc_freeze(c->name, c->config_path);
lxcunlock(c->slock);
+ lxcunlock(c->privlock);
if (ret)
return false;
return true;
if (!c)
return false;
- if (lxclock(c->slock, 0))
+ if (lxclock(c->privlock, 0))
return false;
+ if (lxclock(c->slock, 0)) {
+ lxcunlock(c->privlock);
+ return false;
+ }
ret = lxc_unfreeze(c->name, c->config_path);
lxcunlock(c->slock);
+ lxcunlock(c->privlock);
if (ret)
return false;
return true;
if (!c)
return -1;
- if (lxclock(c->slock, 0))
+ if (lxclock(c->privlock, 0))
return -1;
+ if (lxclock(c->slock, 0)) {
+ lxcunlock(c->privlock);
+ return -1;
+ }
ret = lxc_cmd_get_init_pid(c->name, c->config_path);
lxcunlock(c->slock);
+ lxcunlock(c->privlock);
return ret;
}
fname = alt_file;
if (!fname)
return false;
- if (lxclock(c->slock, 0))
+ if (lxclock(c->privlock, 0))
+ return false;
+ if (lxclock(c->slock, 0)) {
+ lxcunlock(c->privlock);
return false;
+ }
ret = load_config_locked(c, fname);
lxcunlock(c->slock);
+ lxcunlock(c->privlock);
return ret;
}
/* we're going to fork. but since we'll wait for our child, we
* don't need to lxc_container_get */
+ if (lxclock(c->privlock, 0))
+ goto out;
if (lxclock(c->slock, 0)) {
ERROR("failed to grab global container lock for %s\n", c->name);
- goto out;
+ lxcunlock(c->privlock);
+ goto out_unlock1;
}
pid = fork();
out_unlock:
lxcunlock(c->slock);
+out_unlock1:
+ lxcunlock(c->privlock);
out:
if (tpath)
free(tpath);
strcpy(c->name, name);
c->numthreads = 1;
- c->slock = lxc_newlock(name);
- if (!c->slock) {
+ if (!(c->slock = lxc_newlock(c->config_path, name))) {
fprintf(stderr, "failed to create lock\n");
goto err;
}
- c->privlock = lxc_newlock(NULL);
- if (!c->privlock) {
+ if (!(c->privlock = lxc_newlock(NULL, NULL))) {
fprintf(stderr, "failed to alloc privlock\n");
goto err;
}
// private fields
char *name;
char *configfile;
- sem_t *slock;
- sem_t *privlock;
+ struct lxc_lock *slock;
+ struct lxc_lock *privlock;
int numthreads; /* protected by privlock. */
struct lxc_conf *lxc_conf; // maybe we'll just want the whole lxc_handler?
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <pthread.h>
#include "lxclock.h"
#include <malloc.h>
#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <lxc/utils.h>
+#include <lxc/log.h>
#define OFLAG (O_CREAT | O_RDWR)
#define SEMMODE 0660
#define SEMVALUE 1
#define SEMVALUE_LOCKED 0
-#define LXCLOCK_PREFIX "/lxcapi."
+lxc_log_define(lxc_lock, lxc);
-static char *lxclock_name(const char *container)
+pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static char *lxclock_name(const char *p, const char *n)
{
int ret;
- int len = strlen(container) + strlen(LXCLOCK_PREFIX) + 1;
+ // $lxcpath/locks/$lxcname + '\0'
+ int len = strlen(p) + strlen(n) + strlen("/locks/") + 1;
char *dest = malloc(len);
if (!dest)
return NULL;
- ret = snprintf(dest, len, "%s%s", LXCLOCK_PREFIX, container);
+ ret = snprintf(dest, len, "%s/locks", p);
if (ret < 0 || ret >= len) {
free(dest);
return NULL;
}
- return dest;
-}
+ if (mkdir_p(dest, 0755) < 0) {
+ free(dest);
+ return NULL;
+ }
-static void lxcfree_name(char *name)
-{
- if (name)
- free(name);
+ ret = snprintf(dest, len, "%s/locks/%s", p, n);
+ if (ret < 0 || ret >= len) {
+ free(dest);
+ return NULL;
+ }
+ return dest;
}
static sem_t *lxc_new_unnamed_sem(void)
return s;
}
-sem_t *lxc_newlock(const char *name)
+struct lxc_lock *lxc_newlock(const char *lxcpath, const char *name)
{
- char *lname;
- sem_t *lock;
+ struct lxc_lock *l;
+ int ret = pthread_mutex_lock(&thread_mutex);
+ if (ret != 0) {
+ ERROR("pthread_mutex_lock returned:%d %s", ret, strerror(ret));
+ return NULL;
+ }
- if (!name)
- return lxc_new_unnamed_sem();
+ l = malloc(sizeof(*l));
+ if (!l)
+ goto out;
- lname = lxclock_name(name);
- if (!lname)
- return NULL;
- lock = sem_open(lname, OFLAG, SEMMODE, SEMVALUE);
- lxcfree_name(lname);
- if (lock == SEM_FAILED)
- return NULL;
- return lock;
+ if (!name) {
+ l->type = LXC_LOCK_ANON_SEM;
+ l->u.sem = lxc_new_unnamed_sem();
+ goto out;
+ }
+
+ l->type = LXC_LOCK_FLOCK;
+ l->u.f.fname = lxclock_name(lxcpath, name);
+ if (!l->u.f.fname) {
+ free(l);
+ l = NULL;
+ goto out;
+ }
+ l->u.f.fd = -1;
+
+out:
+ pthread_mutex_unlock(&thread_mutex);
+ return l;
}
-int lxclock(sem_t *sem, int timeout)
+int lxclock(struct lxc_lock *l, int timeout)
{
- int ret;
+ int saved_errno = errno;
+ int ret = pthread_mutex_lock(&thread_mutex);
+ if (ret != 0) {
+ ERROR("pthread_mutex_lock returned:%d %s", ret, strerror(ret));
+ return ret;
+ }
- if (!timeout) {
- ret = sem_wait(sem);
- } else {
- struct timespec ts;
- if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
- return -2;
- ts.tv_sec += timeout;
- ret = sem_timedwait(sem, &ts);
+ switch(l->type) {
+ case LXC_LOCK_ANON_SEM:
+ if (!timeout) {
+ ret = sem_wait(l->u.sem);
+ if (ret == -1)
+ saved_errno = errno;
+ } else {
+ struct timespec ts;
+ if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
+ ret = -2;
+ goto out;
+ }
+ ts.tv_sec += timeout;
+ ret = sem_timedwait(l->u.sem, &ts);
+ if (ret == -1)
+ saved_errno = errno;
+ }
+ break;
+ case LXC_LOCK_FLOCK:
+ ret = -2;
+ if (timeout) {
+ ERROR("Error: timeout not supported with flock");
+ ret = -2;
+ goto out;
+ }
+ if (!l->u.f.fname) {
+ ERROR("Error: filename not set for flock");
+ ret = -2;
+ goto out;
+ }
+ if (l->u.f.fd == -1) {
+ l->u.f.fd = open(l->u.f.fname, O_RDWR|O_CREAT,
+ S_IWUSR | S_IRUSR);
+ if (l->u.f.fd == -1) {
+ ERROR("Error opening %s", l->u.f.fname);
+ goto out;
+ }
+ }
+ ret = flock(l->u.f.fd, LOCK_EX);
+ if (ret == -1)
+ saved_errno = errno;
+ break;
}
+out:
+ pthread_mutex_unlock(&thread_mutex);
+ errno = saved_errno;
return ret;
}
-int lxcunlock(sem_t *sem)
+int lxcunlock(struct lxc_lock *l)
{
- if (!sem)
- return -2;
- return sem_post(sem);
+ int saved_errno = errno;
+ int ret = pthread_mutex_lock(&thread_mutex);
+
+ if (ret != 0) {
+ ERROR("pthread_mutex_lock returned:%d %s", ret, strerror(ret));
+ return ret;
+ }
+
+ switch(l->type) {
+ case LXC_LOCK_ANON_SEM:
+ if (!l->u.sem)
+ ret = -2;
+ else
+ ret = sem_post(l->u.sem);
+ saved_errno = errno;
+ break;
+ case LXC_LOCK_FLOCK:
+ if (l->u.f.fd != -1) {
+ if ((ret = flock(l->u.f.fd, LOCK_UN)) < 0)
+ saved_errno = errno;
+ close(l->u.f.fd);
+ l->u.f.fd = -1;
+ } else
+ ret = -2;
+ break;
+ }
+
+ pthread_mutex_unlock(&thread_mutex);
+ errno = saved_errno;
+ return ret;
+}
+
+void lxc_putlock(struct lxc_lock *l)
+{
+ int ret = pthread_mutex_lock(&thread_mutex);
+ if (ret != 0) {
+ ERROR("pthread_mutex_lock returned:%d %s", ret, strerror(ret));
+ return;
+ }
+
+ if (!l)
+ goto out;
+ switch(l->type) {
+ case LXC_LOCK_ANON_SEM:
+ if (l->u.sem)
+ sem_close(l->u.sem);
+ break;
+ case LXC_LOCK_FLOCK:
+ if (l->u.f.fd != -1) {
+ close(l->u.f.fd);
+ l->u.f.fd = -1;
+ }
+ if (l->u.f.fname) {
+ free(l->u.f.fname);
+ l->u.f.fname = NULL;
+ }
+ break;
+ }
+out:
+ pthread_mutex_unlock(&thread_mutex);
}
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
+#include <sys/file.h>
#include <semaphore.h>
#include <string.h>
#include <time.h>
+#define LXC_LOCK_ANON_SEM 1
+#define LXC_LOCK_FLOCK 2
+struct lxc_lock {
+ short type;
+ union {
+ sem_t *sem; // an anonymous semaphore
+ struct {
+ int fd; // fd on which a lock is held (if not -1)
+ char *fname;
+ } f;
+ } u;
+};
+
/*
- * lxc_newlock:
+ * lxc_newlock: Create a new (unlocked) lock.
+ *
* if name is not given, create an unnamed semaphore. We use these
* to protect against racing threads.
* Note that an unnamed sem was malloced by us and needs to be freed.
*
- * If name is given, it is prepended with '/lxcapi.', and used as the
- * name for a system-wide (well, ipcns-wide) semaphore. We use that
- * to protect the containers as represented on disk.
- * A named sem should not be freed.
- *
- * XXX TODO
- * We should probably introduce a lxclock_close() which detecs the type
- * of lock and calls sem_close() or sem_destroy()+free() not as appropriate.
- * For now, it is up to the caller to do so.
- *
* sem is initialized to value of 1
+ * A sem_t * which can be passed to lxclock() and lxcunlock()
+ * will be placed in l->u.sem
*
- * return NULL on failure, else a sem_t * which can be passed to
- * lxclock() and lxcunlock().
+ * If lxcpath and name are given (both must be given if either is
+ * given) then a lockfile is created, $lxcpath/$lxcname/locks/$name.
+ * We use that to protect the containers as represented on disk.
+ * lxc_newlock() for the named lock only allocates the pathname in
+ * memory so we can quickly open+lock it at lxclock.
+ * l->u.f.fname will container the malloc'ed name (which must be
+ * freed when the container is freed), and u.f.fd = -1.
+ *
+ * return lxclock on success, NULL on failure.
*/
-extern sem_t *lxc_newlock(const char *name);
+extern struct lxc_lock *lxc_newlock(const char *lxcpath, const char *name);
/*
* lxclock: take an existing lock. If timeout is 0, wait
* indefinately. Otherwise use given timeout.
* return 0 if we got the lock, -2 on failure to set timeout, or -1
* otherwise in which case errno will be set by sem_wait()).
+ *
+ * Note that timeout is (currently?) only supported for privlock, not
+ * for slock. Since currently there is not a single use of the timeout
+ * (except in the test case) I may remove the support for it in sem as
+ * well.
*/
-extern int lxclock(sem_t *sem, int timeout);
+extern int lxclock(struct lxc_lock *lock, int timeout);
/*
- * lxcunlock: unlock given sem. Return 0 on success. Otherwise returns
- * -1 and sem_post will leave errno set.
+ * lxcunlock: unlock given sem. Return 0 on success, or -2 if we did not
+ * have the lock. Otherwise returns -1 with errno saved from flock
+ * or sem_post function.
*/
-extern int lxcunlock(sem_t *lock);
+extern int lxcunlock(struct lxc_lock *lock);
+
+extern void lxc_putlock(struct lxc_lock *l);
#define mycontainername "lxctest.sem"
#define TIMEOUT_SECS 3
-int timedout;
-int pid_to_kill;
-
-void timeouthandler(int sig)
-{
- // timeout received
- timedout = 1;
- kill(pid_to_kill, SIGTERM);
-}
-
-void starttimer(int secs)
-{
- timedout = 0;
- signal(SIGALRM, timeouthandler);
- alarm(secs);
-}
-void stoptimer(void)
-{
- alarm(0);
- signal(SIGALRM, NULL);
-}
-
-int test_one_lock(sem_t *lock)
-{
- int ret;
- starttimer(TIMEOUT_SECS);
- ret = lxclock(lock, TIMEOUT_SECS*2);
- stoptimer();
- if (ret == 0) {
- lxcunlock(lock);
- return 0;
- }
- if (timedout)
- fprintf(stderr, "%d: timed out waiting for lock\n", __LINE__);
- else
- fprintf(stderr, "%d: failed to get single lock\n", __LINE__);
- return 1;
-}
-
-/*
- * get one lock. Fork a second task to try to get a second lock,
- * with infinite timeout. If our alarm hits, kill the second
- * task. If second task does not
- */
-int test_two_locks(sem_t *lock)
+void test_two_locks(void)
{
- int status;
- int ret;
+ struct lxc_lock *l;
+ pid_t pid;
+ int ret, status;
+ int p[2];
+ char c;
- ret = lxclock(lock, 1);
- if (ret) {
- fprintf(stderr, "%d: Error getting first lock\n", __LINE__);
- return 2;
- }
-
- pid_to_kill = fork();
- if (pid_to_kill < 0) {
- fprintf(stderr, "%d: Failed to fork\n", __LINE__);
- lxcunlock(lock);
- return 3;
- }
-
- if (pid_to_kill == 0) { // child
- ret = lxclock(lock, TIMEOUT_SECS*2);
- if (ret == 0) {
- lxcunlock(lock);
- exit(0);
- }
- fprintf(stderr, "%d: child, was not able to get lock\n", __LINE__);
+ if (pipe(p) < 0)
exit(1);
+ if ((pid = fork()) < 0)
+ exit(1);
+ if (pid == 0) {
+ if (read(p[0], &c, 1) < 0) {
+ perror("read");
+ exit(1);
+ }
+ l = lxc_newlock("/tmp", "lxctest-sem");
+ if (!l) {
+ fprintf(stderr, "%d: child: failed to create lock\n", __LINE__);
+ exit(1);
+ }
+ if (lxclock(l, 0) < 0) {
+ fprintf(stderr, "%d: child: failed to grab lock\n", __LINE__);
+ exit(1);
+ }
+ fprintf(stderr, "%d: child: grabbed lock\n", __LINE__);
+ exit(0);
}
- starttimer(TIMEOUT_SECS);
- waitpid(pid_to_kill, &status, 0);
- stoptimer();
- if (WIFEXITED(status)) {
- // child exited normally - timeout didn't kill it
- if (WEXITSTATUS(status) == 0)
- fprintf(stderr, "%d: child was able to get the lock\n", __LINE__);
- else
- fprintf(stderr, "%d: child timed out too early\n", __LINE__);
- lxcunlock(lock);
- return 1;
- }
- lxcunlock(lock);
- return 0;
-}
-
-/*
- * get one lock. try to get second lock, but asking for timeout. If
- * should return failure. If our own alarm, set at twice the lock
- * request's timeout, hits, then lxclock() did not properly time out.
- */
-int test_with_timeout(sem_t *lock)
-{
- int status;
- int ret = 0;
-
- ret = lxclock(lock, 0);
- if (ret) {
- fprintf(stderr, "%d: Error getting first lock\n", __LINE__);
- return 2;
- }
- pid_to_kill = fork();
- if (pid_to_kill < 0) {
- fprintf(stderr, "%d: Error on fork\n", __LINE__);
- lxcunlock(lock);
- return 2;
+ l = lxc_newlock("/tmp", "lxctest-sem");
+ if (!l) {
+ fprintf(stderr, "%d: failed to create lock\n", __LINE__);
+ exit(1);
}
- if (pid_to_kill == 0) {
- ret = lxclock(lock, TIMEOUT_SECS);
- if (ret == 0) {
- lxcunlock(lock);
- exit(0);
- }
+ if (lxclock(l, 0) < 0) {
+ fprintf(stderr, "%d; failed to get lock\n", __LINE__);
exit(1);
}
- starttimer(TIMEOUT_SECS * 2);
- waitpid(pid_to_kill, &status, 0);
- stoptimer();
- if (!WIFEXITED(status)) {
- fprintf(stderr, "%d: lxclock did not honor its timeout\n", __LINE__);
- lxcunlock(lock);
- return 1;
+ if (write(p[1], &c, 1) < 0) {
+ perror("write");
+ exit(1);
}
- if (WEXITSTATUS(status) == 0) {
- fprintf(stderr, "%d: child was able to get lock, should have failed with timeout\n", __LINE__);
- ret = 1;
+ sleep(3);
+ ret = waitpid(pid, &status, WNOHANG);
+ if (ret == pid) { // task exited
+ if (WIFEXITED(status)) {
+ printf("%d exited normally with exit code %d\n", pid,
+ WEXITSTATUS(status));
+ if (WEXITSTATUS(status) == 0)
+ exit(1);
+ } else
+ printf("%d did not exit normally\n", pid);
+ return;
+ } else if (ret < 0) {
+ perror("waitpid");
+ exit(1);
}
- lxcunlock(lock);
- return ret;
+ kill(pid, SIGKILL);
+ wait(&status);
+ close(p[1]);
+ close(p[0]);
+ lxcunlock(l);
+ lxc_putlock(l);
}
int main(int argc, char *argv[])
{
- int ret, sval, r;
- sem_t *lock;
+ int ret;
+ struct lxc_lock *lock;
- lock = lxc_newlock(NULL);
- if (!lock) {
+ lock = lxc_newlock(NULL, NULL);
+ if (!lock) {
fprintf(stderr, "%d: failed to get unnamed lock\n", __LINE__);
exit(1);
- }
- ret = lxclock(lock, 0);
- if (ret) {
+ }
+ ret = lxclock(lock, 0);
+ if (ret) {
fprintf(stderr, "%d: failed to take unnamed lock (%d)\n", __LINE__, ret);
exit(1);
- }
+ }
- ret = lxcunlock(lock);
- if (ret) {
+ ret = lxcunlock(lock);
+ if (ret) {
fprintf(stderr, "%d: failed to put unnamed lock (%d)\n", __LINE__, ret);
exit(1);
- }
-
- sem_destroy(lock);
- free(lock);
+ }
+ lxc_putlock(lock);
- lock = lxc_newlock(mycontainername);
+ lock = lxc_newlock("/var/lib/lxc", mycontainername);
if (!lock) {
fprintf(stderr, "%d: failed to get lock\n", __LINE__);
exit(1);
}
- r = sem_getvalue(lock, &sval);
- if (!r) {
- fprintf(stderr, "%d: sem value at start is %d\n", __LINE__, sval);
- } else {
- fprintf(stderr, "%d: failed to get initial value\n", __LINE__);
- }
-
- ret = test_one_lock(lock);
- if (ret) {
- fprintf(stderr, "%d: test failed\n", __LINE__);
- goto out;
- }
- r = sem_getvalue(lock, &sval);
- if (!r) {
- fprintf(stderr, "%d: sem value is %d\n", __LINE__, sval);
- } else {
- fprintf(stderr, "%d: failed to get sem value\n", __LINE__);
- }
-
- ret = test_two_locks(lock);
- if (ret) {
- fprintf(stderr, "%d: test failed\n", __LINE__);
- goto out;
- }
- r = sem_getvalue(lock, &sval);
- if (!r) {
- fprintf(stderr, "%d: sem value is %d\n", __LINE__, sval);
- } else {
- fprintf(stderr, "%d: failed to get value\n", __LINE__);
+ struct stat sb;
+ char *pathname = "/var/lib/lxc/locks/" mycontainername;
+ ret = stat(pathname, &sb);
+ if (ret != 0) {
+ fprintf(stderr, "%d: filename %s not created\n", __LINE__,
+ pathname);
+ exit(1);
}
+ lxc_putlock(lock);
- ret = test_with_timeout(lock);
- if (ret) {
- fprintf(stderr, "%d: test failed\n", __LINE__);
- goto out;
- }
- r = sem_getvalue(lock, &sval);
- if (!r) {
- fprintf(stderr, "%d: sem value is %d\n", __LINE__, sval);
- } else {
- fprintf(stderr, "%d: failed to get value\n", __LINE__);
- }
+ test_two_locks();
- fprintf(stderr, "all tests passed\n");
+ fprintf(stderr, "all tests passed\n");
-out:
exit(ret);
}