/*------------------------------------------------------------------------------------------*\
 *   
 *   Copyright (C) 2004 AVM GmbH <fritzbox_info@avm.de>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   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 
\*------------------------------------------------------------------------------------------*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <linux/config.h>
#include <linux/slab.h>
#include <asm/fcntl.h>
#include <asm/ioctl.h>
#include <linux/devfs_fs_kernel.h>
#include <asm/semaphore.h>
#include <asm/errno.h>
#include <linux/wait.h>
#include <linux/tffs.h>
#include <linux/zlib.h>
#include <linux/vmalloc.h>
#if defined(CONFIG_PROC_FS)
#include <linux/proc_fs.h>
#endif /*--- #if defined(CONFIG_PROC_FS) ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
/*--- #define TFFS_DEBUG ---*/
#include "tffs_local.h"

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(TFFS_DEBUG)
static void dump_buffer(char *text, unsigned int len, unsigned char *buffer) {
    int i;
#define dump_buffer_block_size      128
    for(i = 0 ; i < len ; i += dump_buffer_block_size) {
        printk("%s(%u bytes): 0x%x: % *B\n", 
            text,
            len, 
            len, 
            len - i > dump_buffer_block_size ? dump_buffer_block_size : len - i,
            buffer + i);
    }
}
#endif /*--- #if defined(TFFS_DEBUG) ---*/


/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
int tffs_open(struct inode *inode, struct file *filp) {
    int minor = inode->i_rdev & 0xFF;

    DBG((KERN_INFO "%s: tffs_open: MAJOR %u MINOR %u\n", MODULE_NAME, MAJOR(inode->i_rdev), MINOR(inode->i_rdev)));
                
    if(down_interruptible(&tffs_sema)) {
        DBG((KERN_ERR "%s tffs_open: down_interruptible() failed\n", MODULE_NAME));
        return -ERESTARTSYS;
    }
    if(!filp->private_data) {
        filp->private_data = TFFS_Open();
    }
    if(!filp->private_data) {
        DBG((KERN_ERR "%s: tffs_open: TFFS_Open failed\n", MODULE_NAME));
        up(&tffs_sema);
        return -ENOMEM;
    }

    if(filp->f_flags & O_APPEND) {
        DBG((KERN_ERR "%s: tffs_open: TFFS_Open O_APPEND not supported\n", MODULE_NAME));
        up(&tffs_sema);
        return -EFAULT;
    }

    if(filp->f_flags & O_RDWR) {
        DBG(("%s: tffs_open: open O_RDONLY and O_WRONLY simultary not supported\n", MODULE_NAME));
        up(&tffs_sema);
        return -EFAULT;
    }

    /*--------------------------------------------------------------------------------------*\
    \*--------------------------------------------------------------------------------------*/
    if(minor) {
        int status;
        struct _tffs_open *open_data = (struct _tffs_open *)filp->private_data;

        memset((void *)open_data, 0, sizeof(*open_data));
        open_data->id       = minor;
        open_data->z_length = (32 * 1024) + 12;
        open_data->z_Buffer = vmalloc(open_data->z_length);
        if(open_data->z_Buffer == NULL) {
            DBG((KERN_ERR "%s: tffs_open: no memory for z_buffer\n", MODULE_NAME));
            TFFS_Close(filp->private_data);
            up(&tffs_sema);
            return -ENOMEM;
        }

        open_data->stream.data_type = Z_ASCII;
        open_data->stream.workspace = NULL;

        /*----------------------------------------------------------------------------------*\
        \*----------------------------------------------------------------------------------*/
        if(filp->f_flags & O_WRONLY) {
            int workspacesize = zlib_deflate_workspacesize();
            open_data->stream.workspace = vmalloc(workspacesize);
            if(open_data->stream.workspace == NULL) {
                DBG((KERN_ERR "%s: tffs_open: no memory for (write) workspace\n", MODULE_NAME));
                TFFS_Close(filp->private_data);
                vfree(open_data->z_Buffer);
                up(&tffs_sema);
                return -ENOMEM;
            } else {
                DBG((KERN_INFO "%s: tffs_open: %u bytes (write) workspace (0x%p)\n", MODULE_NAME, workspacesize, open_data->stream.workspace));
            }
            DBG((KERN_INFO "%s: open for write only\n", MODULE_NAME));

            open_data->stream.next_in   = NULL;
            open_data->stream.avail_in  = 0;
            open_data->stream.total_in  = 0;

            open_data->stream.next_out  = open_data->z_Buffer;
            open_data->stream.avail_out = open_data->z_length;
            open_data->stream.total_out = 0;

            status = zlib_deflateInit(&open_data->stream, Z_DEFAULT_COMPRESSION);
            if(status != Z_OK) {
                DBG((KERN_ERR "%s: tffs_open: zlib_deflateInit failed, status = %d\n", MODULE_NAME, status));
            } else {
                open_data->init_flag        = 1;
            }
            open_data->init_flag        = 1;
        /*----------------------------------------------------------------------------------*\
        \*----------------------------------------------------------------------------------*/
        } else { /*--- (filp->f_flags & O_RDONLY) ---*/
            int workspacesize = zlib_inflate_workspacesize();
            DBG((KERN_INFO "%s: open for read only\n", MODULE_NAME));

            status = TFFS_Read(open_data, open_data->id, open_data->z_Buffer, &open_data->z_length);

            if(status) {
                vfree(open_data->z_Buffer);
                TFFS_Close(filp->private_data);
                up(&tffs_sema);
                DBG((KERN_INFO "%s: read failed, no file\n", MODULE_NAME));
                return status;
            }
#if defined(TFFS_DEBUG)
            dump_buffer("TFFS", open_data->z_length, open_data->z_Buffer);
#endif /*--- #if defined(TFFS_DEBUG) ---*/

            open_data->stream.next_in   = open_data->z_Buffer;
            open_data->stream.avail_in  = open_data->z_length;
            open_data->stream.total_in  = 0;

            open_data->stream.next_out  = NULL;
            open_data->stream.avail_out = 0;
            open_data->stream.total_out = 0;

            open_data->stream.workspace = vmalloc(workspacesize);
            if(open_data->stream.workspace == NULL) {
                DBG((KERN_ERR "%s: tffs_open: no memory for (read) workspace\n", MODULE_NAME));
                TFFS_Close(filp->private_data);
                vfree(open_data->z_Buffer);
                up(&tffs_sema);
                return -ENOMEM;
            } else {
                DBG((KERN_INFO "%s: tffs_open: %u bytes (read) workspace (0x%p)\n", MODULE_NAME, workspacesize, open_data->stream.workspace));
            }

            status = zlib_inflateInit(&open_data->stream);
            if(status != Z_OK) {
                DBG((KERN_ERR "%s: tffs_open: zlib_inflateInit failed, status = %d\n", MODULE_NAME, status));
            } else {
                open_data->init_flag        = 1;
            }
        /*----------------------------------------------------------------------------------*\
        \*----------------------------------------------------------------------------------*/
        }
    }

    DBG((KERN_INFO "%s: tffs_open: TFFS_Open success flags=0x%x\n", MODULE_NAME, filp->f_flags));
    up(&tffs_sema);
    return 0;
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
int tffs_release(struct inode *inode, struct file *filp) {
    int status = 0;
    struct _tffs_open *open_data = (struct _tffs_open *)filp->private_data;

    if(open_data->id) {
        if(filp->f_flags & O_WRONLY) {
            open_data->stream.next_in   = NULL;
            open_data->stream.avail_in  = 0;
            status = zlib_deflate(&open_data->stream, Z_FINISH);

            DBG((KERN_INFO "Z_FINISH: zlib_deflate(total_in=%u next_in=0x%p avail_in=%u total_out=%u next_out=0x%p avail_out=%u): status = %d\n", 
                    (int)open_data->stream.total_in, open_data->stream.next_in, (int)open_data->stream.avail_in,
                    (int)open_data->stream.total_out, open_data->stream.next_out, (int)open_data->stream.avail_out,
                    status));

            if(status == Z_STREAM_END) {
                zlib_deflateEnd(&open_data->stream);

                if(down_interruptible(&tffs_sema)) {
                    printk(KERN_ERR "%s tffs_release: down_interruptible() failed\n", MODULE_NAME);
                } else {
                    DBG((KERN_INFO "%s: tffs_release: write %u bytes for id 0x%x\n", MODULE_NAME, (int)open_data->stream.total_out, open_data->id));

#if defined(TFFS_DEBUG)
                    dump_buffer("TFFS", open_data->stream.total_out, open_data->z_Buffer);
#endif /*--- #if defined(TFFS_DEBUG) ---*/

                    TFFS_Write(open_data, open_data->id, open_data->z_Buffer, open_data->stream.total_out, 0);
                    up(&tffs_sema);
                }
                status = 0; /*--- kein Fehler ---*/
            } else {
                status = -EFAULT;
            }
        } else {
            zlib_inflateEnd(&open_data->stream);
        }
        if(open_data->stream.workspace)
            vfree(open_data->stream.workspace);
        if(open_data->z_Buffer)
            vfree(open_data->z_Buffer);
    }

    TFFS_Close(filp->private_data);
    return status;
}

