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)

Comentários