tech-userlevel archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: Shell tutorial added



Hi Perry, et al.,

On Tue, Aug 24, 2010 at 6:40 AM, Perry E. Metzger 
<perry%piermont.com@localhost> wrote:
> On Mon, 23 Aug 2010 20:33:05 -0700 Bruce Korb <bkorb%gnu.org@localhost> wrote:
>> On Mon, Aug 23, 2010 at 4:15 PM, Perry E. Metzger
>> <perry%piermont.com@localhost> wrote:
>> By the way, I'm curious about plans for fmemopen.
>
> I'm unaware of any such plans. However, if you have an
> implementation, why don't you send it to tech-userlevel along with a
> pointer to the fact that it is an implementation of the stated posix
> spec at the given URL? (You don't have commit access yourself,
> correct?)

That's correct.  No access.
Attached is the fmemopen.c file, unifdef-ed to just include the BSD funopen
implementation.  (My original leverages off of either funopen or fopencookie.
I'm platform agnostic.)

This implementation is a strict superset of POSIX:

>> a la the pending POSIX spec:
>> http://www.opengroup.org/onlinepubs/9699919799/functions/fmemopen.html

because the POSIX spec disallows extending the buffer size and I saw
no reason for the constraint.  Relaxing that restriction and allowing for
access to the buffer pointer eliminates the need for open_memstream(), too.
I prefer not having any more interfaces than necessary. :)  In fact, I'd
prefer a user space fs that had a kernel module that could tell the open()
code to finish open processing by calling a library space open function,
a la funopen and fopencookie.  This would permit this call to work:
   fd = open("/path/to/userfs/additional/path/info", O_RDWR.)
but I have digressed.

By using the interface exactly as described in the POSIX docs, you get exactly
the POSIX behavior.  Additional behaviors are available by using an ssize_t
size with a negative value and by calling fmemioctl().

Regards, Bruce
/**
 * \file fmemopen.c
 *
 * Copyright (c) 2004-2010 by Bruce Korb.  All rights reserved.
 *
 * Time-stamp:      "2010-08-20 12:59:32 bkorb"
 *
 * This code was inspired from software written by
 *   Hanno Mueller, kontakt%hanno.de@localhost
 * and completely rewritten by Bruce Korb, bkorb%gnu.org@localhost
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the name of any other contributor
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>

#include <fcntl.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "libfmem.h"

   typedef fpos_t  seek_off_t;
   typedef fpos_t  seek_ret_t;

     typedef int     (cookie_read_function_t )(void *, char *, int);
     typedef int     (cookie_write_function_t)(void *, char const *, int);
     typedef fpos_t  (cookie_seek_function_t )(void *, fpos_t, int);
     typedef int     (cookie_close_function_t)(void *);


#define PROP_TABLE \
_Prop_( read,       "Read from buffer"        ) \
_Prop_( write,      "Write to buffer"         ) \
_Prop_( append,     "Append to buffer okay"   ) \
_Prop_( binary,     "byte data - not string"  ) \
_Prop_( create,     "allocate the string"     ) \
_Prop_( truncate,   "start writing at start"  ) \
_Prop_( allocated,  "we allocated the buffer" ) \
_Prop_( fixed_size, "writes do not append"    )

#ifndef NUL
# define NUL '\0'
#endif

#define _Prop_(n,s)   BIT_ID_ ## n,
typedef enum { PROP_TABLE BIT_CT } fmem_flags_e;
#undef  _Prop_

#define FLAG_BIT(n)  (1 << BIT_ID_ ## n)

typedef unsigned long mode_bits_t;
typedef unsigned char buf_bytes_t;

typedef struct {
    mode_bits_t    mode;
    buf_bytes_t *  buffer;
    size_t         buf_size;    /* Full size of buffer */
    size_t         next_ix;     /* Current position */
    size_t         eof;         /* End Of File */
    size_t         pg_size;     /* size of a memory page.
                                   Future architectures allow it to vary
                                   by memory region. */
} fmem_cookie_t;

typedef struct {
    FILE *          fp;
    fmem_cookie_t * cookie;
} cookie_fp_map_t;

typedef enum { FMO_FALSE = 0, FMO_TRUE = 1 } fmo_bool;

static cookie_fp_map_t const * map    = NULL;
static unsigned int            map_ct = 0;
static unsigned int            map_alloc_ct = 0;

/* = = = START-STATIC-FORWARD = = = */
static int
fmem_getmode(char const *pMode, mode_bits_t *pRes);

static int
fmem_extend(fmem_cookie_t *pFMC, size_t new_size);

static ssize_t
fmem_read(void *cookie, void *pBuf, size_t sz);

static ssize_t
fmem_write(void *cookie, const void *pBuf, size_t sz);

static seek_ret_t
fmem_seek(void * cookie, seek_off_t offset, int dir);

static int
fmem_close(void * cookie);

static fmo_bool
fmem_config_user_buf(fmem_cookie_t * pFMC, void * buf, ssize_t len);

static fmo_bool
fmem_alloc_buf(fmem_cookie_t * pFMC, ssize_t len);
/* = = = END-STATIC-FORWARD = = = */

#ifdef TEST_FMEMOPEN
  static fmem_cookie_t* saved_cookie = NULL;
#endif

/**
 * Convert a mode string into mode bits.
 */
static int
fmem_getmode(char const * mode, mode_bits_t *pRes)
{
    if (mode == NULL)
        return 1;

    switch (*mode) {
    case 'a': *pRes = FLAG_BIT(write) | FLAG_BIT(append);
              break;
    case 'w': *pRes = FLAG_BIT(write) | FLAG_BIT(truncate);
              break;
    case 'r': *pRes = FLAG_BIT(read);
              break;
    default:  return EINVAL;
    }

    /*
     *  If someone wants to supply a "wxxbxbbxbb+" mode string, I don't care.
     */
    for (;;) {
        switch (*++mode) {
        case '+': *pRes |= FLAG_BIT(read) | FLAG_BIT(write);
                  if (mode[1] != NUL)
                      return EINVAL;
                  break;
        case NUL: break;
        case 'b': *pRes |= FLAG_BIT(binary); continue;
        case 'x': continue;
        default:  return EINVAL;
        }
        break;
    }

    return 0;
}

/**
 * Extend the space associated with an fmem file.
 */
static int
fmem_extend(fmem_cookie_t *pFMC, size_t new_size)
{
    size_t ns = (new_size + (pFMC->pg_size - 1)) & (~(pFMC->pg_size - 1));

    /*
     *  We can expand the buffer only if we are in append mode.
     */
    if (pFMC->mode & FLAG_BIT(fixed_size))
        goto no_space;

    if ((pFMC->mode & FLAG_BIT(allocated)) == 0) {
        /*
         *  Previously, this was a user supplied buffer.  We now move to one
         *  of our own.  The user is responsible for the earlier memory.
         */
        void* bf = malloc(ns);
        if (bf == NULL)
            goto no_space;

        memcpy(bf, pFMC->buffer, pFMC->buf_size);
        pFMC->buffer = bf;
        pFMC->mode  |= FLAG_BIT(allocated);
    }
    else {
        void* bf = realloc(pFMC->buffer, ns);
        if (bf == NULL)
            goto no_space;

        pFMC->buffer = bf;
    }

    /*
     *  Unallocated file space is set to zeros.  Emulate that.
     */
    memset(pFMC->buffer + pFMC->buf_size, 0, ns - pFMC->buf_size);
    pFMC->buf_size = ns;
    return 0;

 no_space:
    errno = ENOSPC;
    return -1;
}

/**
 * Handle file system callback to read data from our string.
 */
static ssize_t
fmem_read(void *cookie, void *pBuf, size_t sz)
{
    fmem_cookie_t *pFMC = cookie;

    if (pFMC->next_ix + sz > pFMC->eof) {
        if (pFMC->next_ix >= pFMC->eof)
            return (sz > 0) ? -1 : 0;
        sz = pFMC->eof - pFMC->next_ix;
    }

    memcpy(pBuf, pFMC->buffer + pFMC->next_ix, sz);

    pFMC->next_ix += sz;

    return sz;
}

/**
 * Handle file system callback to write data to our string
 */
static ssize_t
fmem_write(void *cookie, const void *pBuf, size_t sz)
{
    fmem_cookie_t *pFMC = cookie;
    int add_nul_char;

    /*
     *  In append mode, always seek to the end before writing.
     */
    if (pFMC->mode & FLAG_BIT(append))
        pFMC->next_ix = pFMC->eof;

    /*
     *  Only add a NUL character if:
     *
     *  * we are not in binary mode
     *  * there are data to write
     *  * the last character to write is not already NUL
     */
    add_nul_char =
           ((pFMC->mode & FLAG_BIT(binary)) != 0)
        && (sz > 0)
        && (((char*)pBuf)[sz - 1] != NUL);

    {
        size_t next_pos = pFMC->next_ix + sz + add_nul_char;
        if (next_pos > pFMC->buf_size) {
            if (fmem_extend(pFMC, next_pos) != 0) {
                /*
                 *  We could not extend the memory.  Try to write some data.
                 *  Fail if we are either at the end or not writing data.
                 */
                if ((pFMC->next_ix >= pFMC->buf_size) || (sz == 0))
                    return -1; /* no space at all.  errno is set. */

                /*
                 *  Never add the NUL for a truncated write.  "sz" may be
                 *  unchanged or limited here.
                 */
                add_nul_char = 0;
                sz = pFMC->buf_size - pFMC->next_ix;
            }
        }
    }

    memcpy(pFMC->buffer + pFMC->next_ix, pBuf, sz);

    pFMC->next_ix += sz;

    /*
     *  Check for new high water mark and remember it.  Add a NUL if
     *  we do that and if we have a new high water mark.
     */
    if (pFMC->next_ix > pFMC->eof) {
        pFMC->eof = pFMC->next_ix;
        if (add_nul_char)
            /*
             *  There is space for this NUL.  The "add_nul_char" is not part of
             *  the "sz" that was added to "next_ix".
             */
            pFMC->buffer[ pFMC->eof ] = NUL;
    }

    return sz;
}

/**
 * Handle file system callback to set a new current position
 */
static seek_ret_t
fmem_seek(void * cookie, seek_off_t offset, int dir)
{
    size_t new_pos;
    fmem_cookie_t *pFMC = cookie;

    /*
     *  BSD interface:  offset passed by value, returned as retval.
     */
    switch (dir) {
    case SEEK_SET: new_pos = offset;  break;
    case SEEK_CUR: new_pos = pFMC->next_ix  + offset;  break;
    case SEEK_END: new_pos = pFMC->eof - offset;  break;

    default:
        goto seek_oops;
    }

    if ((signed)new_pos < 0)
        goto seek_oops;

    if (new_pos > pFMC->buf_size) {
        if (fmem_extend(pFMC, new_pos))
            return -1; /* errno is set */
    }

    pFMC->next_ix = new_pos;

    return new_pos;

 seek_oops:
    errno = EINVAL;
    return -1;
}

/**
 * Free up the memory associated with an fmem file.
 * If the user is managing the space, then the allocated bit is set.
 */
static int
fmem_close(void * cookie)
{
    fmem_cookie_t   * pFMC = cookie;
    cookie_fp_map_t * pmap = (void *)map;
    unsigned int      mct  = map_ct;

    while (mct-- != 0) {
        if (pmap->cookie == cookie) {
            *pmap = map[--map_ct];
            break;
        }
        pmap++;
    }

    if (mct > map_ct)
        errno = EINVAL;

    if (pFMC->mode & FLAG_BIT(allocated))
        free(pFMC->buffer);
    free(pFMC);

    return 0;
}

/**
 * Configure the user supplied buffer.
 */
static fmo_bool
fmem_config_user_buf(fmem_cookie_t * pFMC, void * buf, ssize_t len)
{
    /*
     *  User allocated buffer.  User responsible for disposal.
     */
    if (len == 0) {
        free(pFMC);
        errno = EINVAL;
        return FMO_FALSE;
    }

    pFMC->buffer = (buf_bytes_t*)buf;

    /*  Figure out where our "next byte" and EOF are.
     *  Truncated files start at the beginning.
     */
    if (pFMC->mode & FLAG_BIT(truncate)) {
        /*
         *  "write" mode
         */
        pFMC->eof = \
            pFMC->next_ix = 0;
    }

    else if (pFMC->mode & FLAG_BIT(binary)) {
        pFMC->eof = len;
        pFMC->next_ix    = (pFMC->mode & FLAG_BIT(append)) ? len : 0;

    } else {
        /*
         * append or read text mode -- find the end of the buffer
         * (the first NUL character)
         */
        buf_bytes_t *p = (buf_bytes_t*)buf;

        pFMC->eof = 0;
        while ((*p != NUL) && (++(pFMC->eof) < len))  p++;
        pFMC->next_ix =
            (pFMC->mode & FLAG_BIT(append)) ? pFMC->eof : 0;
    }

    /*
     *  text mode - NUL terminate buffer, if it fits.
     */
    if (  ((pFMC->mode & FLAG_BIT(binary)) == 0)
       && (pFMC->next_ix < len)) {
        pFMC->buffer[pFMC->next_ix] = NUL;
    }

    pFMC->buf_size = len;
    return FMO_TRUE;
}

/**
 * Allocate an initial buffer for fmem.
 */
static fmo_bool
fmem_alloc_buf(fmem_cookie_t * pFMC, ssize_t len)
{
    /*
     *  We must allocate the buffer.  If "len" is zero, set it to page size.
     */
    pFMC->mode |= FLAG_BIT(allocated);
    if (len == 0)
        len = pFMC->pg_size;

    /*
     *  Unallocated file space is set to NULs.  Emulate that.
     */
    pFMC->buffer = calloc((size_t)1, (size_t)len);
    if (pFMC->buffer == NULL) {
        errno = ENOMEM;
        free(pFMC);
        return FMO_FALSE;
    }

    /*
     *  We've allocated the buffer.  The end of file and next entry
     *  are both zero.
     */
    pFMC->next_ix  = 0;
    pFMC->eof      = 0;
    pFMC->buf_size = len;
    return FMO_TRUE;
}

/*=export_func fmemopen
 *
 *  what:  Open a stream to a string
 *
 *  arg: + void*   + buf  + buffer to use for i/o +
 *  arg: + ssize_t + len  + size of the buffer +
 *  arg: + char*   + mode + mode string, a la fopen(3C) +
 *
 *  ret-type:  FILE*
 *  ret-desc:  a stdio FILE* pointer
 *
 *  err:  NULL is returned and errno is set to @code{EINVAL} or @code{ENOSPC}.
 *
 *  doc:
 *
 *  This function requires underlying @var{libc} functionality:
 *  either @code{fopencookie(3GNU)} or @code{funopen(3BSD)}.
 *
 *  If @var{buf} is @code{NULL}, then a buffer is allocated.  The initial
 *  allocation is @var{len} bytes.  If @var{len} is less than zero, then the
 *  buffer will be reallocated as more space is needed.  Any allocated
 *  memory is @code{free()}-ed when @code{fclose(3C)} is called.
 *
 *  If @code{buf} is not @code{NULL}, then @code{len} must not be zero.
 *  It may still be less than zero to indicate that the buffer may
 *  be reallocated.
 *
 *  The mode string is interpreted as follows.  If the first character of
 *  the mode is:
 *
 *  @table @code
 *  @item a
 *  Then the string is opened in "append" mode.  In binary mode, "appending"
 *  will begin from the end of the initial buffer.  Otherwise, appending will
 *  start at the first NUL character in the initial buffer (or the end of the
 *  buffer if there is no NUL character).  Do not use fixed size buffers
 *  (negative @var{len} lengths) in append mode.
 *
 *  @item w
 *  Then the string is opened in "write" mode.  Any initial buffer is presumed
 *  to be empty.
 *
 *  @item r
 *  Then the string is opened in "read" mode.
 *  @end table
 *
 *  @noindent
 *  If it is not one of these three, the open fails and @code{errno} is
 *  set to @code{EINVAL}.  These initial characters may be followed by:
 *
 *  @table @code
 *  @item +
 *  The buffer is marked as updatable and both reading and writing is enabled.
 *
 *  @item b
 *  The I/O is marked as "binary" and a trailing NUL will not be inserted
 *  into the buffer.  Without this mode flag, one will be inserted after the
 *  @code{EOF}, if it fits.  It will fit if the buffer is extensible (the
 *  provided @var{len} was negative).  This mode flag has no effect if
 *  the buffer is opened in read-only mode.
 *
 *  @item x
 *  This is ignored.
 *  @end table
 *
 *  @noindent
 *  Any other letters following the inital 'a', 'w' or 'r' will cause an error.
=*/
FILE *
fmo_fmemopen(void * buf, ssize_t len, char const * mode)
{
    fmem_cookie_t *pFMC;

    {
        mode_bits_t bits;

        if (fmem_getmode(mode, &bits) != 0) {
            return NULL;
        }

        pFMC = malloc(sizeof(fmem_cookie_t));
        if (pFMC == NULL) {
            errno = ENOMEM;
            return NULL;
        }

        pFMC->mode = bits;
    }

    /*
     *  Two more mode bits that do not come from the mode string:
     *  a negative size implies fixed size buffer and a NULL
     *  buffer pointer means we must allocate (and free) it.
     */
    if (len <= 0) {
        /*
         *  We only need page size if we might extend an allocation.
         */
        len = -len;
        pFMC->pg_size = getpagesize();
    }

    else {
        pFMC->mode |= FLAG_BIT(fixed_size);
    }

    if (buf != NULL) {
        if (! fmem_config_user_buf(pFMC, buf, len))
            return NULL;

    } else if ((pFMC->mode & (FLAG_BIT(append) | FLAG_BIT(truncate))) == 0) {
        /*
         *  Not appending and not truncating.  We must be reading.
         *  We also have no user supplied buffer.  Nonsense.
         */
        errno = EINVAL;
        free(pFMC);
        return NULL;
    }

    else if (! fmem_alloc_buf(pFMC, len))
        return NULL;

#ifdef TEST_FMEMOPEN
    saved_cookie = pFMC;
#endif

    {
        FILE * res;

        cookie_read_function_t* pRd = (pFMC->mode & FLAG_BIT(read))
            ?  (cookie_read_function_t*)fmem_read  : NULL;
        cookie_write_function_t* pWr = (pFMC->mode & FLAG_BIT(write))
            ? (cookie_write_function_t*)fmem_write : NULL;

        res = funopen(pFMC, pRd, pWr, fmem_seek, fmem_close);
        if (res == NULL)
            return res;

        if (++map_ct >= map_alloc_ct) {
            void * p = (map_alloc_ct > 0)
                ? realloc((void *)map, (map_alloc_ct += 4) * sizeof(*map))
                : malloc((map_alloc_ct = 4) * sizeof(*map));

            if (p == NULL) {
                fclose(res);
                errno = ENOMEM; /* "fclose" set it to "EINVAL". */
                return NULL;
            }

            map = p;
        }

        {
            cookie_fp_map_t * p = (void *)(map + map_ct - 1);
            p->fp = res;
            p->cookie = pFMC;
        }

        return res;
    }
}

/*=export_func fmemioctl
 *
 *  what:  perform an ioctl on a FILE* descriptor
 *
 *  arg: + FILE* + fp      + file pointer  +
 *  arg: + int   + req     + ioctl command +
 *  arg: + ...   + varargs + arguments for command +
 *
 *  ret-type:  int
 *  ret-desc:  zero on success, otherwise error in errno
 *
 *  err:  errno is set to @code{EINVAL} or @code{ENOSPC}.
 *
 *  doc:
 *
 *  The file pointer passed in must have been returned by fmo_fmemopen.
=*/
int
fmemioctl(FILE * fp, int req, ...)
{
    fmem_cookie_t * cookie;
    fmemc_get_buf_addr_t * gba;

    switch (req) {
    case IOCTL_FMEMC_GET_BUF_ADDR:
        break;

    default:
        /*
         *  It is not any of the IOCTL commands we know about.
         */
        errno = EINVAL;
        return -1;
    }

    {
        cookie_fp_map_t const * pmap = map;
        unsigned int mct  = map_ct;

        for (;;) {
            if (mct-- == 0) {
                /*
                 *  fmemopen didn't create this FILE*, so it is invalid.
                 */
                errno = EINVAL;
                return -1;
            }
            if (pmap->fp == fp)
                break;
            pmap++;
        }

        cookie = pmap->cookie;
    }

    {
        va_list ap;
        va_start(ap, req);
        gba = va_arg(ap, fmemc_get_buf_addr_t *);
        va_end(ap);
    }

    gba->buffer   = (char *)(cookie->buffer);
    gba->buf_size = cookie->buf_size;
    gba->eof      = cookie->eof;
    if (gba->own != FMEMC_GBUF_LEAVE_OWNERSHIP)
        cookie->mode &= ~FLAG_BIT(allocated);
    return 0;
}

/*
 * Local Variables:
 * mode: C
 * c-file-style: "stroustrup"
 * indent-tabs-mode: nil
 * End:
 * end of agen5/fmemopen.c */

Attachment: fmemopen.3
Description: Binary data

Attachment: fmemioctl.3
Description: Binary data

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "libfmem.h"

static char const text[] =
"The quick brown fox jumped over lazy dogs.\n";

static int const txtlen = sizeof(text) - 1;
fmemc_get_buf_addr_t gba = {
    .own = FMEMC_GBUF_TAKE_OWNERSHIP
};

void die(char const * msg) {
    fprintf(stderr, "fatal test error: %s\n", msg);
    exit(EXIT_FAILURE);
}

void ck_buf(char const * ck) {
    int ct = 1;
    do  {
        if (strncmp(ck, text, txtlen) != 0)
            die("miscompare");
        ck += txtlen;
    } while (++ct <= 3);
    if (strcmp(ck, "\n") != 0) die("miscompare");
}

void ck_file(FILE * fp) {
    char buf[8 * sizeof(text)];
    int ct;

    rewind(fp);
    ct = fread(buf, 1, sizeof(buf), fp);

    if (ct != (3 * txtlen) + 1) {
        fprintf(stderr, "data len is %d, not %d\n", ct,
                (3 * txtlen) + 1);
        exit(EXIT_FAILURE);
    }

    ck_buf(buf);
}

int main(int argc, char ** argv) {
    FILE * fp;
    fp = ag_fmemopen(NULL, 0, "w+");
    if (fp == NULL) {
        perror("fmemopen failed");
        exit(EXIT_FAILURE); }
    fprintf(fp, "%s", text);
    fwrite(text, txtlen, 1, fp);
    fputs(text, fp);
    fputc('\n', fp);
    fflush(fp);
    if (ag_fmemioctl(fp, IOCTL_FMEMC_GET_BUF_ADDR, &gba) != 0) {
        perror("fmemioctl failed");
        exit(EXIT_FAILURE);
    }

    if (gba.eof != (3 * txtlen) + 1) {
        fprintf(stderr, "eof is %d, not %d\n", gba.eof,
                (3 * txtlen) + 1);
        exit(EXIT_FAILURE);
    }

    ck_buf(gba.buffer);
    ck_file(fp);
    fclose(fp);
    free(gba.buffer);
    fputs("data written correctly\n", stdout);
    return EXIT_SUCCESS;
}


Home | Main Index | Thread Index | Old Index