/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library 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 GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Michael Zucchi
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "camel-lock-client.h"
#include "camel-lock-helper.h"
#include "camel-object.h"
#define d(x)
#define CHECK_CALL(x) G_STMT_START { \
if ((x) == -1) { \
g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \
} \
} G_STMT_END
static GMutex lock_lock;
#define LOCK() g_mutex_lock(&lock_lock)
#define UNLOCK() g_mutex_unlock(&lock_lock)
static gint lock_sequence;
static gint lock_helper_pid = -1;
static gint lock_stdin_pipe[2], lock_stdout_pipe[2];
static gint read_n (gint fd, gpointer buffer, gint inlen)
{
gchar *p = buffer;
gint len, left = inlen;
do {
len = read (fd, p, left);
if (len == -1) {
if (errno != EINTR)
return -1;
} else {
left -= len;
p += len;
}
} while (left > 0 && len != 0);
return inlen - left;
}
static gint write_n (gint fd, gpointer buffer, gint inlen)
{
gchar *p = buffer;
gint len, left = inlen;
do {
len = write (fd, p, left);
if (len == -1) {
if (errno != EINTR)
return -1;
} else {
left -= len;
p += len;
}
} while (left > 0);
return inlen;
}
static gint
lock_helper_init (GError **error)
{
gint i, dupfd1, dupfd2;
lock_stdin_pipe[0] = -1;
lock_stdin_pipe[1] = -1;
lock_stdout_pipe[0] = -1;
lock_stdout_pipe[1] = -1;
if (pipe (lock_stdin_pipe) == -1
|| pipe (lock_stdout_pipe) == -1) {
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Cannot build locking helper pipe: %s"),
g_strerror (errno));
if (lock_stdin_pipe[0] != -1)
close (lock_stdin_pipe[0]);
if (lock_stdin_pipe[1] != -1)
close (lock_stdin_pipe[1]);
if (lock_stdout_pipe[0] != -1)
close (lock_stdout_pipe[0]);
if (lock_stdout_pipe[1] != -1)
close (lock_stdout_pipe[1]);
return -1;
}
lock_helper_pid = fork ();
switch (lock_helper_pid) {
case -1:
close (lock_stdin_pipe[0]);
close (lock_stdin_pipe[1]);
close (lock_stdout_pipe[0]);
close (lock_stdout_pipe[1]);
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Cannot fork locking helper: %s"),
g_strerror (errno));
return -1;
case 0:
close (STDIN_FILENO);
dupfd1 = dup (lock_stdin_pipe[0]);
close (STDOUT_FILENO);
dupfd2 = dup (lock_stdout_pipe[1]);
close (lock_stdin_pipe[0]);
close (lock_stdin_pipe[1]);
close (lock_stdout_pipe[0]);
close (lock_stdout_pipe[1]);
for (i = 3; i < 255; i++)
close (i);
execl (CAMEL_LIBEXECDIR "/camel-lock-helper-" API_VERSION, "camel-lock-helper", NULL);
if (dupfd1 != -1)
close (dupfd1);
if (dupfd2 != -1)
close (dupfd2);
/* it'll pick this up when it tries to use us */
exit (255);
default:
close (lock_stdin_pipe[0]);
close (lock_stdout_pipe[1]);
/* so the child knows when we vanish */
CHECK_CALL (fcntl (lock_stdin_pipe[1], F_SETFD, FD_CLOEXEC));
CHECK_CALL (fcntl (lock_stdout_pipe[0], F_SETFD, FD_CLOEXEC));
}
return 0;
}
gint
camel_lock_helper_lock (const gchar *path,
GError **error)
{
struct _CamelLockHelperMsg *msg;
gint len = strlen (path);
gint res = -1;
gint retry = 3;
LOCK ();
if (lock_helper_pid == -1) {
if (lock_helper_init (error) == -1) {
UNLOCK ();
return -1;
}
}
msg = alloca (len + sizeof (*msg));
again:
msg->magic = CAMEL_LOCK_HELPER_MAGIC;
msg->seq = lock_sequence;
msg->id = CAMEL_LOCK_HELPER_LOCK;
msg->data = len;
memcpy (msg + 1, path, len);
write_n (lock_stdin_pipe[1], msg, len + sizeof (*msg));
do {
/* should also have a timeout here? cancellation? */
len = read_n (lock_stdout_pipe[0], msg, sizeof (*msg));
if (len == 0) {
/* child quit, do we try ressurect it? */
res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
/* if the child exited, this should get it, waidpid returns 0 if the child hasn't */
if (waitpid (lock_helper_pid, NULL, WNOHANG) > 0) {
lock_helper_pid = -1;
close (lock_stdout_pipe[0]);
close (lock_stdin_pipe[1]);
lock_stdout_pipe[0] = -1;
lock_stdin_pipe[1] = -1;
}
goto fail;
}
if (msg->magic != CAMEL_LOCK_HELPER_RETURN_MAGIC
|| msg->seq > lock_sequence) {
res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
d (printf ("lock child protocol error\n"));
g_set_error (
error, CAMEL_ERROR,
CAMEL_ERROR_GENERIC,
_("Could not lock '%s': protocol "
"error with lock-helper"), path);
goto fail;
}
} while (msg->seq < lock_sequence);
if (msg->seq == lock_sequence) {
switch (msg->id) {
case CAMEL_LOCK_HELPER_STATUS_OK:
d (printf ("lock child locked ok, id is %d\n", msg->data));
res = msg->data;
break;
default:
g_set_error (
error, CAMEL_ERROR,
CAMEL_ERROR_GENERIC,
_("Could not lock '%s'"), path);
d (printf ("locking failed ! status = %d\n", msg->id));
break;
}
} else if (retry > 0) {
d (printf ("sequence failure, lost message? retry?\n"));
retry--;
goto again;
} else {
g_set_error (
error, CAMEL_ERROR,
CAMEL_ERROR_GENERIC,
_("Could not lock '%s': protocol "
"error with lock-helper"), path);
}
fail:
lock_sequence++;
UNLOCK ();
return res;
}
gint camel_lock_helper_unlock (gint lockid)
{
struct _CamelLockHelperMsg *msg;
gint res = -1;
gint retry = 3;
gint len;
d (printf ("unlocking lock id %d\n", lockid));
LOCK ();
/* impossible to unlock if we haven't locked yet */
if (lock_helper_pid == -1) {
UNLOCK ();
return -1;
}
msg = alloca (sizeof (*msg));
again:
msg->magic = CAMEL_LOCK_HELPER_MAGIC;
msg->seq = lock_sequence;
msg->id = CAMEL_LOCK_HELPER_UNLOCK;
msg->data = lockid;
write_n (lock_stdin_pipe[1], msg, sizeof (*msg));
do {
/* should also have a timeout here? cancellation? */
len = read_n (lock_stdout_pipe[0], msg, sizeof (*msg));
if (len == 0) {
/* child quit, do we try ressurect it? */
res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
if (waitpid (lock_helper_pid, NULL, WNOHANG) > 0) {
lock_helper_pid = -1;
close (lock_stdout_pipe[0]);
close (lock_stdin_pipe[1]);
lock_stdout_pipe[0] = -1;
lock_stdin_pipe[1] = -1;
}
goto fail;
}
if (msg->magic != CAMEL_LOCK_HELPER_RETURN_MAGIC
|| msg->seq > lock_sequence) {
goto fail;
}
} while (msg->seq < lock_sequence);
if (msg->seq == lock_sequence) {
switch (msg->id) {
case CAMEL_LOCK_HELPER_STATUS_OK:
d (printf ("lock child unlocked ok\n"));
res = 0;
break;
default:
d (printf ("locking failed !\n"));
break;
}
} else if (retry > 0) {
d (printf ("sequence failure, lost message? retry?\n"));
lock_sequence++;
retry--;
goto again;
}
fail:
lock_sequence++;
UNLOCK ();
return res;
}
#if 0
gint main (gint argc, gchar **argv)
{
gint id1, id2;
d (printf ("locking started\n"));
lock_helper_init ();
id1 = camel_lock_helper_lock ("1 path 1");
if (id1 != -1) {
d (printf ("lock ok, unlock\n"));
camel_lock_helper_unlock (id1);
}
id1 = camel_lock_helper_lock ("2 path 1");
id2 = camel_lock_helper_lock ("2 path 2");
camel_lock_helper_unlock (id2);
camel_lock_helper_unlock (id1);
id1 = camel_lock_helper_lock ("3 path 1");
id2 = camel_lock_helper_lock ("3 path 2");
camel_lock_helper_unlock (id1);
}
#endif