/*****************************************************************************
 * Filename:    cryptfs/misc.c                                               *
 * Description: Miscellaneous routines                                       *
 * Copyright:   2009 by Alexander Motzkau                                    *
 *****************************************************************************/

#include <errno.h>
#include <mntent.h>
#include <grp.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>

#include "misc.h"
#include "options.h"

/*  Because we should not include kernel headers like <linux/fs.h> */
#define BLKGETSIZE _IO(0x12,96)

void *xmalloc(size_t size)
{
    void *ptr;
    
    ptr = malloc(size);
    if(ptr == NULL)
    {
	errmsg("Out of memory.\n");
	exit(1);
    }
    
    return ptr;
}

void xfree(void *ptr)
{
    free(ptr);
}

char *xmallocstr(char *str)
{
    size_t len;
    char* res;
    
    len = strlen(str) + 1;
    res = (char*) xmalloc(len);
    memcpy(res, str, len);

    return res;
}

int get_blockdev_size(char *devname, unsigned long *size)
{
    FILE *dev;
    dev = fopen(devname, "rb");
    if(dev == NULL || ioctl(fileno(dev), BLKGETSIZE, size) < 0)
    {
	if(dev != NULL)
	    fclose(dev);
	return 0;
    }

    fclose(dev);
    return 1;
}

int is_mounted(char *devname)
{
    FILE *mtab;
    int result;

    result = 0;
    mtab = setmntent("/proc/mounts","rt");
    if(mtab != NULL)
    {
	struct mntent* mnt;
	
	while(1)
	{
	    mnt = getmntent(mtab);
	    if(mnt == NULL)
		break;
	
	    if(!strcmp(mnt->mnt_fsname, devname))
	    {
		result = 1;
		break;
	    }
	}
	endmntent(mtab);
    }
    
    return result;
}

void setuidnam(char *uid, char *gid, struct configuration *config)
{
    gid_t gidnr;
    
    if(gid != NULL)
    {
	struct group* grpent;
	grpent = getgrnam(gid);
	
	if(grpent == NULL)
	{
	    errmsg("Unknown group '%s'.\n", gid);
	    exit(1);
	}
	
	gidnr = grpent->gr_gid;
    }
    else
	gidnr = 0; /* Just for the warning. */
    
    if(uid != NULL)
    {
	struct passwd* pwent;
	pwent = getpwnam(uid);
            
	if(pwent == NULL)
	{
    	    errmsg("Unknown user '%s'.\n", uid);
	    exit(1);
	}
    
	initgroups(pwent->pw_name, pwent->pw_gid);
	
	if(gid == NULL)
	    setgid(pwent->pw_gid);
	else
	    setgid(gidnr);
	
	setuid(pwent->pw_uid);
        chdir(pwent->pw_dir);
    
	setenv("HOME", pwent->pw_dir, 1);
	setenv("USER", pwent->pw_name, 1);
    }
    else
    {
	if(config->suid)
	    setuid(config->caller_uid);

        if(gid != NULL)
	    setgid(gidnr);
	else if(config->sgid)
	    setgid(config->caller_gid);
    }
}

int use_syslog = 0;
int syslog_open = 0;

void errmsg(const char* fmt, ...)
{
    if(use_syslog)
    {
	int size;
	
	if(!syslog_open)
	{
	    openlog("cryptfs", LOG_CONS|LOG_PERROR|LOG_PID, LOG_USER);
	    syslog_open = 1;
	}
    
	va_list args;
    
	size = 100;
	while(1)
	{
	    int n;
	    char *buf;

	    buf = xmalloc(size);
	
	    va_start(args, fmt);
	    n = vsnprintf(buf, size, fmt, args);
	    va_end(args);
	
	    if(n >= 0 && n < size)
	    {
		syslog(LOG_ERR, "%s", buf);
	        xfree(buf);
		break;
	    }
	
	    if(n >= 0)
		size = n+1;
	    else
		size *= 2;
	    
	    xfree(buf);
	}
    }
    else
    {
	va_list args;
	
	va_start(args, fmt);
	vfprintf(stderr, fmt, args);
	va_end(args);
    }
}

void errnomsg(const char* s)
{
    errmsg("%s: %s\n", s, strerror(errno));
}

enum bool t_resolve(enum tristate a, ...)
{
    va_list args;
    enum tristate result;
    
    result = a;

    va_start(args, a);
    while(result == t_unspecified)
	result = va_arg(args, enum tristate);
    va_end(args);
    
    return result;
}
