aboutsummaryrefslogblamecommitdiffstats
path: root/camel/camel-lock-client.c
blob: fbbed821929b174fec060a27d23c1e519538ba7b (plain) (tree)
1
2
3
4
5
6
7
8
9






                                                                        
                                                                    
                                                        































































































                                                                      

              




                                                                               
                          





















                                                                         

                                      


































































































































































































                                                                                                              
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2001 Ximian Inc.
 *
 * Authors: Michael Zucchi <notzed@ximian.com>
 *
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of version 2 of the GNU General Public 
 * License 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>

#include <errno.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <unistd.h>
#include <fcntl.h>

#ifdef ENABLE_THREADS
#include <pthread.h>
#endif

#include "camel-exception.h"
#include "camel-lock-helper.h"
#include "camel-lock-client.h"

#define d(x)

/* dunno where this fucking thing is got from */
/* see also camel-lock.c */
#define _(x) (x)

#ifdef ENABLE_THREADS
static pthread_mutex_t lock_lock = PTHREAD_MUTEX_INITIALIZER;
#define LOCK() pthread_mutex_lock(&lock_lock)
#define UNLOCK() pthread_mutex_unlock(&lock_lock)
#else
#define LOCK()
#define UNLOCK()
#endif

static int lock_sequence;
static int lock_helper_pid = -1;
static int lock_stdin_pipe[2], lock_stdout_pipe[2];

static int read_n(int fd, void *buffer, int inlen)
{
    char *p = buffer;
    int 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 int write_n(int fd, void *buffer, int inlen)
{
    char *p = buffer;
    int 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 int camel_lock_helper_init(CamelException *ex)
{
    int i;

    if (pipe(lock_stdin_pipe) == -1
        || pipe(lock_stdout_pipe) == -1) {
        camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
                     _("Cannot build locking helper pipe: %s"),
                     strerror(errno));
        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]);
        camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
                     _("Cannot fork locking helper: %s"),
                     strerror(errno));
        return -1;
    case 0:
        close(STDIN_FILENO);
        dup(lock_stdin_pipe[0]);
        close(STDOUT_FILENO);
        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_SBINDIR "/camel-lock-helper", "camel-lock-helper", NULL);
        d(fprintf(stderr, "shit, couldn't exec lock helper!\n"));
        /* 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 */
        fcntl(lock_stdin_pipe[1], F_SETFD, FD_CLOEXEC);
        fcntl(lock_stdout_pipe[0], F_SETFD, FD_CLOEXEC);
    }

    return 0;
}

int camel_lock_helper_lock(const char *path, CamelException *ex)
{
    struct _CamelLockHelperMsg *msg;
    int len = strlen(path);
    int res = -1;
    int retry = 3;

    LOCK();

    if (lock_helper_pid == -1) {
        if (camel_lock_helper_init(ex) == -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"));
            camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
                         _("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:
            camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
                         _("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 {
        camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
                     _("Could not lock '%s': protocol error with lock-helper"), path);
    }

fail:
    lock_sequence++;

    UNLOCK();

    return res;
}

int camel_lock_helper_unlock(int lockid)
{
    struct _CamelLockHelperMsg *msg;
    int res = -1;
    int retry = 3;
    int 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
int main(int argc, char **argv)
{
    int id1, id2;

    d(printf("locking started\n"));
    camel_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