linux fmemopen, bsd funopen

Enquanto desenvolvia um sistema, me deparei com um problema entre plataformas, relacionado ao uso e manipulação de arquivos temporários em memória.

Ao invés de gravar um arquivo temporário no disco e ir populando com o conteúdo, meu caso exigia que esse arquivo fosse populado em memória para uso posterior, e a API do Linux (via libc) fornece a função fmemopen(3).

Porém, meu ambiente de desenvolvimento é no Mac OS X, e devido ao tipo do software, era inevitátel tanto desenvolver como testar tudo nessa mesma máquina, para então posteriormente colocá-lo em produção em um Linux. O problema é que o OS X herdou a API do BSD, que ao invés de fmemopen, fornece apenas o funopen(3).

Por esse motivo, acabei escrevendo uma versão bem simples do fmemopen do Linux para usar no OS X, usando funopen internamente. O único detalhe, e provavelmente a diferença entre a versão original do fmemopen pra minha, é que a minha simplesmente ignora o modo de acesso ao arquivo (“r”, “w”, etc). O fato é que agora posso continuar desenvolvendo tudo no OS X e depois compilar no Linux obtendo o mesmo resultado.

fmem.h:

/*
 * fmem.h : fmemopen() on top of BSD's funopen()
 * 20081017 AF
 */

#ifndef _FMEM_H
#define _FMEM_H

#ifndef linux
#include <stdio.h>
extern FILE *fmemopen(void *buf, size_t size, const char *mode);
#else
#define _GNU_SOURCE
#endif

#endif /* fmem.h */

fmem.c:

/*
 * fmem.c : fmemopen() on top of BSD's funopen()
 * 20081017 AF
 */

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

#ifndef linux
struct fmem {
    size_t pos;
    size_t size;
    char *buffer;
};
typedef struct fmem fmem_t;

static int readfn(void *handler, char *buf, int size)
{
    int count = 0;
    fmem_t *mem = handler;
    size_t available = mem->size - mem->pos;

    if(size > available) size = available;
    for(count=0; count < size; mem->pos++, count++)
        buf[count] = mem->buffer[mem->pos];

    return count;
}

static int writefn(void *handler, const char *buf, int size)
{
    int count = 0;
    fmem_t *mem = handler;
    size_t available = mem->size - mem->pos;

    if(size > available) size = available;
    for(count=0; count < size; mem->pos++, count++)
        mem->buffer[mem->pos] = buf[count];

    return count; // ? count : size;
}

static fpos_t seekfn(void *handler, fpos_t offset, int whence)
{
    size_t pos;
    fmem_t *mem = handler;

    switch(whence) {
        case SEEK_SET: pos = offset; break;
        case SEEK_CUR: pos = mem->pos + offset; break;
        case SEEK_END: pos = mem->size + offset; break;
        default: return -1;
    }

    if(pos < 0 || pos > mem->size) return -1;

    mem->pos = pos;
    return (fpos_t) pos;
}

static int closefn(void *handler)
{
    free(handler);
    return 0;
}

/* simple, but portable version of fmemopen for OS X / BSD */
FILE *fmemopen(void *buf, size_t size, const char *mode)
{
    fmem_t *mem = (fmem_t *) malloc(sizeof(fmem_t));

    memset(mem, 0, sizeof(fmem_t));
    mem->size = size, mem->buffer = buf;
    return funopen(mem, readfn, writefn, seekfn, closefn);
}
#endif

E finalmente, um programa simples para testar o uso da função, que compila e produz o exato mesmo resultado tanto no Linux como no OS X – não testei em outros BSDs, mas suponho que deva funcionar também.

test.c:

/*
 * test.c : memory fp test
 * 20081212 AF
 */

#include "fmem.h"
#include <stdio.h>
#include <string.h>

int main()
{
    FILE *fp;
    char buff[128], temp[128];

    memset(buff, 0, sizeof buff);

    fp = fmemopen(buff, sizeof buff, "r+");
    fprintf(fp, "hello world");
    fseek(fp, 0, SEEK_SET);

    memset(temp, 0, sizeof buff);
    fgets(temp, sizeof temp, fp);

    fclose(fp);

    fprintf(stdout, "read: [%s]\n", temp);

    return 0;
}

Para compilar, basta o usar o seguinte Makefile:

CC=gcc
RM=rm -f
NAME=test

.SUFFIXES = .o
OBJECTS = test.o fmem.o

.c.o:
    $(CC) -Wall -c -o $@ $< all: $(OBJECTS)     $(CC) -Wall -o $(NAME) $(OBJECTS) clean:     $(RM) $(NAME) $(OBJECTS)[/sourcecode]


One Comment on “linux fmemopen, bsd funopen”

  1. Caio Ariede disse:

    ae!

    muito interessante, não trabalho com C (ainda), mas curto muito.

    com certeza irei acompanhar o seu blog, já vai pro blogroll.

    abraços. :)


Deixe um comentário