/*****************************************************************************
 * Filename:    cryptfs/dm.c                                                 *
 * Description: Interface to the device mapper                               *
 * Copyright:   2009 by Alexander Motzkau                                    *
 *****************************************************************************/

#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <libdevmapper.h>
#include <sys/types.h>

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

enum bool setup_crypt(struct map* map, void* key, int keylen, struct map_options* override, struct configuration* config)
{
    struct storage_block* block;
    struct dm_task* dmt;
    unsigned long devpos;
    char *idptr;
    
    block = find_storage(map->name, config);
    if(block == NULL)
	return false;

    if(!(dmt = dm_task_create(DM_DEVICE_CREATE)))
    {
	errmsg("%s: Couldn't create device mapper task.\n", map->name);
	return false;
    }
    
    if(!dm_task_set_name(dmt, map->dname))
    {
	dm_task_destroy(dmt);
	errmsg("%s: Couldn't set task name.\n", map->name);
	return false;
    }

    devpos = 0;
        
    while(block!=NULL)
    {
	static char *hex = "0123456789abcdef";
	char buf[1024];
	char *pars;
	unsigned long devsize;
	int plen, pos, i;
	
	/* Parameter: <cipher> <key> <iv-offset> <device> <start>
    	     <start> points to the begin within <device>
	     <iv-offset> should be the offset within the resulting (mapped)
			 device
	*/
	
	if(!config->noenc)
	    plen = 7 + strlen(block->entry->cipher) + keylen*2
		     + sizeof(devpos)*10/3;
	else	
	    plen = 4;
	
	plen += strlen(block->device->device) + 
	    sizeof(block->entry->start)*10/3; /* Estimated length */
	
	if(plen > 1024)
	    pars = xmalloc(plen);
	else
	{
	    pars = buf;
	    plen = 1024;
	}
	
	if(config->noenc)
	{
	    if(snprintf(pars, plen, "%s %lu", 
		block->device->device, block->entry->start) >= plen)
	    {
		if(pars != buf)
		    xfree(pars);
	        dm_task_destroy(dmt);
		errmsg("%s: Line too long.\n", map->name);
	        return false;
	    }
	}
	else
	{    
	    pos = strlen(block->entry->cipher);
	    memcpy(pars, block->entry->cipher, pos);
	    pars[pos++] = ' ';
	
	    for(i=0; i<keylen; i++)
	    {
		int k = ((unsigned char*)key)[i];
		pars[pos++] = hex[k>>4];
		pars[pos++] = hex[k & 15];
	    }
	
	    pars[pos++] = ' ';
	
	    if(snprintf(pars+pos, plen - pos, "%lu %s %lu", devpos,
		block->device->device, block->entry->start) >= plen - pos)
	    {
		if(pars != buf)
		    xfree(pars);
	        dm_task_destroy(dmt);
		errmsg("%s: Line too long.\n", map->name);
	        return false;
	    }
	}
	
	devsize = block->entry->size;
	if(!devsize)
	{
	    if(!get_blockdev_size(block->device->device, &devsize))
	    {
		errnomsg(block->device->device);
		if(pars != buf)
		    xfree(pars);
	        dm_task_destroy(dmt);
		return false;
	    }
	}
	
	if(!dm_task_add_target(dmt, devpos, devsize,
	    config->noenc?"linear":"crypt", pars))
	{
	    if(pars != buf)
		xfree(pars);
	    dm_task_destroy(dmt);
	    errmsg("%s: Couldn't add storage.\n", map->name);
	    return false;
	}
	
	devpos += devsize;

	if(pars != buf)
	    xfree(pars);
	
	block = block->next;
    }
    
    if(T_RESOLVE2(override->ro, map->opts.ro) == t_true)
	dm_task_set_ro(dmt);

    if((idptr = override->uid) || (idptr = map->opts.uid))
    {
	struct passwd* pwent;
	pwent = getpwnam(idptr);
	
	if(pwent == NULL)
        {
	    dm_task_destroy(dmt);
	    errmsg("%s: Unknown user '%s'.\n", map->name, idptr);
	    return false;
	}
	
	dm_task_set_uid(dmt, pwent->pw_uid);
    }

    if((idptr = override->gid) || (idptr = map->opts.gid))
    {
	struct group* grpent;
	grpent = getgrnam(idptr);
	
	if(grpent == NULL)
        {
	    dm_task_destroy(dmt);
	    errmsg("%s: Unknown group '%s'.\n", map->name, idptr);
	    return false;
	}
	
	dm_task_set_gid(dmt, grpent->gr_gid);
    }
    
        
    if(override->mode)
	dm_task_set_mode(dmt, override->mode);
    else if(map->opts.mode)
	dm_task_set_mode(dmt, map->opts.mode);
    
    if(!dm_task_run(dmt))
    {
	dm_task_destroy(dmt);
	errmsg("%s: Couldn't run task.\n", map->name);
	return false;
    }
    
    dm_task_destroy(dmt);
    
    dm_lib_release();
    
    return true;
}

void remove_crypt(struct map* map, struct configuration* config)
{
    struct dm_task* dmt;
    
    if(!(dmt = dm_task_create(DM_DEVICE_REMOVE)))
    {
	errmsg("%s: Couldn't create device mapper task.\n", map->name);
	return;
    }
    
    if(!dm_task_set_name(dmt, map->dname))
    {
	dm_task_destroy(dmt);
	errmsg("%s: Couldn't set task name.\n", map->name);
	return;
    }

    if(!dm_task_run(dmt))
	errmsg("%s: Couldn't run task.\n", map->name);

    dm_task_destroy(dmt);

    dm_lib_release();
}

char *get_crypt_name(struct map* map)
{
    char *dmname;
        
    if(map->dname[0] == '/')
    {
	int mlen;

	mlen = strlen(map->dname);
	dmname = xmalloc(mlen + 1);
	memcpy(dmname, map->dname, mlen + 1);
    }
    else
    {
        const char* dmdir;
        int dmdlen, mlen;
            
        dmdir = dm_dir();
	dmdlen = strlen(dmdir);
        mlen = strlen(map->dname);
	
        dmname = xmalloc(dmdlen + mlen + 2);
            
        memcpy(dmname, dmdir, dmdlen);
        dmname[dmdlen] = '/';
        memcpy(dmname + dmdlen + 1, map->dname, mlen + 1);
    }
    
    return dmname;
}
