/*------------------------------------------------------------------------------------------*\
 *   
 *   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 <asm/smplock.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"

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void *tffs_alloc(voidpf opaque, uInt items, uInt size) {
    void *ptr;
    DBG((KERN_INFO "tffs_alloc(%s, 0x%x, 0x%x):\n", opaque, items, size));
    ptr = kmalloc(items * size, GFP_KERNEL);
    if(ptr) {
        memset(ptr, 0, items * size);
        DBG((KERN_INFO " ptr=0x%x\n", ptr));
    } else {
        DBG((KERN_ERR " failed\n"));
    }
    return ptr;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void tffs_free(voidpf opaque, voidpf address) {
    DBG((KERN_INFO "tffs_free(%s, 0x%x)\n", opaque, address));
    kfree(address);
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
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, (inode->i_rdev >> 8), minor));

    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.zalloc    = tffs_alloc;
        open_data->stream.zfree     = tffs_free;

        /*----------------------------------------------------------------------------------*\
        \*----------------------------------------------------------------------------------*/
        if(filp->f_flags & O_WRONLY) {
            DBG((KERN_INFO "%s: open for write only\n", MODULE_NAME));
            open_data->stream.opaque    = "write";

            open_data->stream.next_in   = Z_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 = deflateInit2 (&open_data->stream,
                                       Z_DEFAULT_COMPRESSION,
                                       Z_DEFLATED,
                                       9, /*--- (fast) smallest buffers ---*/
                                       2, /*--- use little memory ---*/
                                       Z_DEFAULT_STRATEGY 
                                      );

            open_data->init_flag        = 1;
        } else { /*--- (filp->f_flags & O_RDONLY) ---*/
            DBG((KERN_INFO "%s: open for read only\n", MODULE_NAME));
            open_data->stream.opaque    = "read";

            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;
            }

            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  = Z_NULL;
            open_data->stream.avail_out = 0;
            open_data->stream.total_out = 0;

            status = inflateInit2(&open_data->stream, 9);
            open_data->init_flag        = 1;
        /*----------------------------------------------------------------------------------*\
        \*----------------------------------------------------------------------------------*/
        }
    }

    MOD_INC_USE_COUNT;
    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   = Z_NULL;
            open_data->stream.avail_in  = 0;
            status = deflate(&open_data->stream, Z_FINISH);

            DBG((KERN_INFO "Z_FINISH: 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) {
                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));
                    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 {
            inflateEnd(&open_data->stream);
        }
        vfree(open_data->z_Buffer);
    }

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

