/*
    zfhs.c - FHS compliant device locking
    Copyright (C) 2002-2012  Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include <libziaint.h>

#include <zdebug.h>
#include <zerror.h>
#include <zfhs.h>
#include <zpath.h>

#include <errno.h>
#include <glib.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

#ifdef Z_HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef Z_HAVE_WINSOCK2_H
#include <winsock2.h>
#endif
#ifdef Z_HAVE_WINDOWS_H
#include <windows.h>
#endif

#define FHS_RETURN(code) { ret=(code);goto x; }

#ifndef WIN32
static int zfhs_kill_stale(FILE *f, const char *lockfile){
    int pid, ret, err;
    char s[256];
    
    s[sizeof(s) - 1]='\0';
    if (!fgets(s, sizeof(s) - 1, f)) return 0;
    pid = atoi(s);
//    dbg("pid=%d\n", pid);
    if (!pid) return -4;
	ret = kill(pid, 0);
	err = errno;
	dbg("zfhs_kill_stale(mypid=%d): kill(%d) ret=%d errno=%d EINVAL=%d ESRCH=%d\n", getpid(), pid, ret, errno, EINVAL, ESRCH);
	if (!ret) {
		return -7;  	// process alive
	}else{
		if (err == EINVAL) return -4; // device is locked, details unknown
		if (err != ESRCH) return -7; // proces alive
	}

    if (unlink(lockfile)) {
		dbg("failed to unlink lockfile\n");
		return -1;
	}
	
	dbg("lockfile unlinked\n");
    return 0;
}
#endif

#ifndef WIN32
static char *zfhs_get_lockfile(const char *device){
	struct stat st;
	char *c = strrchr(device, '/');
    if (!c) return NULL;

	if (stat("/var/lock/lockdev", &st) == 0){
		if ((st.st_mode & S_IWGRP) || (st.st_mode & S_IWOTH)) return g_strdup_printf("/var/lock/lockdev/LCK..%s", c + 1);
		// it's no writable
	}
	// don't exist
	return g_strdup_printf("/var/lock/LCK..%s", c + 1);
}
#endif

int zfhs_lock(const char *device, int ignore_stale){
    int ret = 0;
#ifndef WIN32
	//__CYGWIN__	
    char *lockfile = NULL;
    FILE *f = NULL;
    
    lockfile = zfhs_get_lockfile(device);
	if (!lockfile) FHS_RETURN(-2); 

    f = fopen(lockfile, "rt");
    if (f){
        if (!ignore_stale){
            FHS_RETURN(-3);
        }else{
            if (zfhs_kill_stale(f, lockfile)){
                FHS_RETURN(-4);
            }
        }
        fclose(f); /* real unlink is here */
    }

    f=fopen(lockfile, "wt");
    if (!f){
        FHS_RETURN(-5);
    }else{
        char *filename = z_binary_file_name();
        const char *fn = z_filename(filename);

        fprintf(f, "%10d %s ", (int)getpid(), fn); 
        fprintf(f, "%d\n", (int)getuid());
        g_free(filename);
    }
x:;
    if (f) fclose(f);
    if (lockfile) g_free(lockfile);
#endif    
    return ret;
}


int zfhs_unlock(const char *device){
    int ret = 0;
#ifndef WIN32
	//__CYGWIN__
    char *lockfile = NULL;
    
    if (!device) return ret;

	lockfile = zfhs_get_lockfile(device);
	if (!lockfile) FHS_RETURN(-2); 

    if (unlink(lockfile)) FHS_RETURN(-6);
x:;
    if (lockfile) g_free(lockfile);
#endif    
    return ret;
}


char *zfhs_strdup_error(int err, const char *device){
    switch (err){
        case -1:
#ifndef WIN32
            return z_strdup_strerror(errno);
#else
			return g_strdup("zfhs_strdup_error: case -1 unsupported on this platform");
#endif

        case -2:
    		return g_strdup_printf("Bad filename %s", device);
        case -3:
        case -4:
            return g_strdup_printf("Device %s is locked", device);
        case -5:
            return g_strdup_printf("Can't create lock file for %s", device);
        case -6:
            return g_strdup_printf("Can't delete lock file for %s", device);
        case -7:
            return g_strdup_printf("Device %s is locked, process alive", device);
        default:
            break;
    }
    return g_strdup_printf("Unknown lock error %d on device %s", err, device);
}
