/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1997-2011. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
/*
* RAM File operations
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
/* Operations */
/* defined "file" functions */
#define RAM_FILE_OPEN 1
#define RAM_FILE_READ 2
#define RAM_FILE_LSEEK 3
#define RAM_FILE_WRITE 4
#define RAM_FILE_FSYNC 9
#define RAM_FILE_TRUNCATE 14
#define RAM_FILE_PREAD 17
#define RAM_FILE_PWRITE 18
#define RAM_FILE_FDATASYNC 19
/* other operations */
#define RAM_FILE_GET 30
#define RAM_FILE_SET 31
#define RAM_FILE_GET_CLOSE 32 /* get_file/close */
#define RAM_FILE_COMPRESS 33 /* compress file */
#define RAM_FILE_UNCOMPRESS 34 /* uncompress file */
#define RAM_FILE_UUENCODE 35 /* uuencode file */
#define RAM_FILE_UUDECODE 36 /* uudecode file */
#define RAM_FILE_SIZE 37 /* get file size */
#define RAM_FILE_ADVISE 38 /* predeclare the access
* pattern for file data */
/* possible new operations include:
DES_ENCRYPT
DES_DECRYPT
CRC-32, CRC-16, CRC-CCITT
IP-CHECKSUM
*/
/*
* Open modes for RAM_FILE_OPEN.
*/
#define RAM_FILE_MODE_READ 1
#define RAM_FILE_MODE_WRITE 2 /* Implies truncating file
* when used alone. */
#define RAM_FILE_MODE_READ_WRITE 3
/*
* Seek modes for RAM_FILE_LSEEK.
*/
#define RAM_FILE_SEEK_SET 0
#define RAM_FILE_SEEK_CUR 1
#define RAM_FILE_SEEK_END 2
/* Return codes */
#define RAM_FILE_RESP_OK 0
#define RAM_FILE_RESP_ERROR 1
#define RAM_FILE_RESP_DATA 2
#define RAM_FILE_RESP_NUMBER 3
#define RAM_FILE_RESP_INFO 4
#include <stdio.h>
#include <ctype.h>
#include <limits.h>
#include "sys.h"
#include "erl_driver.h"
#include "zlib.h"
#include "gzio.h"
#ifndef NULL
#define NULL ((void*)0)
#endif
#define BFILE_BLOCK 1024
typedef unsigned char uchar;
static ErlDrvData rfile_start(ErlDrvPort, char*);
static int rfile_init(void);
static void rfile_stop(ErlDrvData);
static void rfile_command(ErlDrvData, char*, ErlDrvSizeT);
struct erl_drv_entry ram_file_driver_entry = {
rfile_init,
rfile_start,
rfile_stop,
rfile_command,
NULL,
NULL,
"ram_file_drv"
};
/* A File is represented as a array of bytes, this array is
reallocated when needed. A possibly better implementation
whould be to have a vector of blocks. This may be implemented
when we have the commandv/driver_outputv
*/
typedef struct ram_file {
ErlDrvPort port; /* the associcated port */
int flags; /* flags read/write */
ErlDrvBinary* bin; /* binary to hold binary file */
char* buf; /* buffer start (in binary) */
ErlDrvSSizeT size; /* buffer size (allocated) */
ErlDrvSSizeT cur; /* current position in buffer */
ErlDrvSSizeT end; /* end position in buffer */
} RamFile;
#ifdef LOADABLE
static int rfile_finish(DriverEntry* drv)
{
return 0;
}
DriverEntry* driver_init(void *handle)
{
ram_file_driver_entry.handle = handle;
ram_file_driver_entry.driver_name = "ram_file_drv";
ram_file_driver_entry.finish = rfile_finish;
ram_file_driver_entry.init = rfile_init;
ram_file_driver_entry.start = rfile_start;
ram_file_driver_entry.stop = rfile_stop;
ram_file_driver_entry.output = rfile_command;
ram_file_driver_entry.ready_input = NULL;
ram_file_driver_entry.ready_output = NULL;
return &ram_file_driver_entry;
}
#endif
static int rfile_init(void)
{
return 0;
}
static ErlDrvData rfile_start(ErlDrvPort port, char* buf)
{
RamFile* f;
if ((f = (RamFile*) driver_alloc(sizeof(RamFile))) == NULL) {
errno = ENOMEM;
return ERL_DRV_ERROR_ERRNO;
}
f->port = port;
f->flags = 0;
f->bin = NULL;
f->buf = NULL;
f->size = f->cur = f->end = 0;
return (ErlDrvData)f;
}
static void rfile_stop(ErlDrvData e)
{
RamFile* f = (RamFile*)e;
if (f->bin != NULL)
driver_free_binary(f->bin);
driver_free(f);
}
/*
* Sends back an error reply to Erlang.
*/
static int error_reply(RamFile *f, int err)
{
char response[256]; /* Response buffer. */
char* s;
char* t;
/*
* Contents of buffer sent back:
*
* +-----------------------------------------+
* | RAM_FILE_RESP_ERROR | Posix error id string |
* +-----------------------------------------+
*/
response[0] = RAM_FILE_RESP_ERROR;
for (s = erl_errno_id(err), t = response+1; *s; s++, t++)
*t = tolower(*s);
driver_output2(f->port, response, t-response, NULL, 0);
return 0;
}
static int reply(RamFile *f, int ok, int err)
{
if (!ok)
error_reply(f, err);
else {
char c = RAM_FILE_RESP_OK;
driver_output2(f->port, &c, 1, NULL, 0);
}
return 0;
}
static int numeric_reply(RamFile *f, ErlDrvSSizeT result)
{
char tmp[5];
/*
* Contents of buffer sent back:
*
* +-----------------------------------------------+
* | RAM_FILE_RESP_NUMBER | 32-bit number (big-endian) |
* +-----------------------------------------------+
*/
tmp[0] = RAM_FILE_RESP_NUMBER;
put_int32(result, tmp+1);
driver_output2(f->port, tmp, sizeof(tmp), NULL, 0);
return 0;
}
/* install bin as the new binary reset all pointer */
static void ram_file_set(RamFile *f, ErlDrvBinary *bin,
ErlDrvSSizeT bsize, ErlDrvSSizeT len)
{
f->size = bsize;
f->buf = bin->orig_bytes;
f->cur = 0;
f->end = len;
f->bin = bin;
}
static int ram_file_init(RamFile *f, char *buf, ErlDrvSSizeT count, int *error)
{
ErlDrvSSizeT bsize;
ErlDrvBinary* bin;
if (count < 0) {
*error = EINVAL;
return -1;
}
if ((bsize = (count+BFILE_BLOCK+(BFILE_BLOCK>>1)) & ~(BFILE_BLOCK-1))
< 0) {
bsize = INT_MAX;
}
if (f->bin == NULL)
bin = driver_alloc_binary(bsize);
else
bin = driver_realloc_binary(f->bin, bsize);
if (bin == NULL) {
*error = ENOMEM;
return -1;
}
sys_memzero(bin->orig_bytes, bsize);
sys_memcpy(bin->orig_bytes, buf, count);
ram_file_set(f, bin, bsize, count);
return count;
}
static ErlDrvSSizeT ram_file_expand(RamFile *f, ErlDrvSSizeT size, int *error)
{
ErlDrvSSizeT bsize;
ErlDrvBinary* bin;
if (size < 0) {
*error = EINVAL;
return -1;
}
if ((bsize = (size+BFILE_BLOCK+(BFILE_BLOCK>>1)) & ~(BFILE_BLOCK-1))
< 0) {
bsize = INT_MAX;
}
if (bsize <= f->size)
return f->size;
else {
if ((bin = driver_realloc_binary(f->bin, bsize)) == NULL) {
*error = ENOMEM;
return -1;
}
sys_memzero(bin->orig_bytes+f->size, bsize - f->size);
f->size = bsize;
f->buf = bin->orig_bytes;
f->bin = bin;
return bsize;
}
}
static ErlDrvSSizeT ram_file_write(RamFile *f, char *buf, ErlDrvSSizeT len,
ErlDrvSSizeT *location, int *error)
{
ErlDrvSSizeT cur = f->cur;
if (!(f->flags & RAM_FILE_MODE_WRITE)) {
*error = EBADF;
return -1;
}
if (location) cur = *location;
if (cur < 0 || len < 0 || cur+len < 0) {
*error = EINVAL;
return -1;
}
if (cur+len > f->size && ram_file_expand(f, cur+len, error) < 0) {
return -1;
}
if (len) sys_memcpy(f->buf+cur, buf, len);
cur += len;
if (cur > f->end) f->end = cur;
if (! location) f->cur = cur;
return len;
}
static ErlDrvSSizeT ram_file_read(RamFile *f, ErlDrvSSizeT len, ErlDrvBinary **bp,
ErlDrvSSizeT *location, int *error)
{
ErlDrvBinary* bin;
ErlDrvSSizeT cur = f->cur;
if (!(f->flags & RAM_FILE_MODE_READ)) {
*error = EBADF;
return -1;
}
if (location) cur = *location;
if (cur < 0 || len < 0) {
*error = EINVAL;
return -1;
}
if (cur < f->end) {
if (len > f->end-cur) len = f->end - cur;
} else {
len = 0; /* eof */
}
if ((bin = driver_alloc_binary(len)) == NULL) {
*error = ENOMEM;
return -1;
}
if (len) sys_memcpy(bin->orig_bytes, f->buf+cur, len);
*bp = bin;
if (! location) f->cur = cur + len;
return len;
}
static ErlDrvSSizeT ram_file_seek(RamFile *f, ErlDrvSSizeT offset, int whence,
int *error)
{
ErlDrvSSizeT pos;
if (f->flags == 0) {
*error = EBADF;
return -1;
}
switch(whence) {
case RAM_FILE_SEEK_SET: pos = offset; break;
case RAM_FILE_SEEK_CUR: pos = f->cur + offset; break;
case RAM_FILE_SEEK_END: pos = f->end + offset; break;
default: *error = EINVAL; return -1;
}
if (pos < 0) {
*error = EINVAL;
return -1;
}
return f->cur = pos;
}
#define UUMASK(x) ((x)&0x3F)
#define uu_encode(x) (UUMASK(x)+32)
/* calculate max number of quadrauple bytes given max line length */
#define UULINE(n) ( (((n)-1) / 4) * 3)
#define UNIX_LINE 61 /* 61 character lines => 45 uncoded => 60 coded */
#define uu_pack(p, c1, c2, c3) \
(p)[0] = uu_encode((c1) >> 2), \
(p)[1] = uu_encode(((c1) << 4) | ((c2) >> 4)), \
(p)[2] = uu_encode(((c2) << 2) | ((c3) >> 6)), \
(p)[3] = uu_encode(c3)
static int ram_file_uuencode(RamFile *f)
{
ErlDrvSSizeT code_len = UULINE(UNIX_LINE);
ErlDrvSSizeT len = f->end;
ErlDrvSSizeT usize = 4*((len+2)/3) + 2*((len+code_len-1)/code_len) + 2;
ErlDrvBinary* bin;
uchar* inp;
uchar* outp;
ErlDrvSSizeT count = 0;
if ((bin = driver_alloc_binary(usize)) == NULL)
return error_reply(f, ENOMEM);
outp = (uchar*)bin->orig_bytes;
inp = (uchar*)f->buf;
while(len > 0) {
int c1, c2, c3;
int n = (len >= code_len) ? code_len : len;
len -= n;
*outp++ = uu_encode(UUMASK(n));
count++;
while (n >= 3) {
c1 = inp[0];
c2 = inp[1];
c3 = inp[2];
uu_pack(outp, c1, c2, c3);
inp += 3; n -= 3;
outp += 4; count += 4;
}
if (n == 2) {
c1 = inp[0];
c2 = inp[1];
uu_pack(outp, c1, c2, 0);
inp += 2;
outp += 4; count += 4;
}
else if (n == 1) {
c1 = inp[0];
uu_pack(outp, c1, 0, 0);
inp += 1;
outp += 4; count += 4;
}
*outp++ = '\n';
count++;
}
*outp++ = ' '; /* this end of file 0 length !!! */
*outp++ = '\n';
count += 2;
ASSERT(count == usize);
driver_free_binary(f->bin);
ram_file_set(f, bin, usize, count);
return numeric_reply(f, count);
}
#define uu_decode(x) ((x)-32)
static int ram_file_uudecode(RamFile *f)
{
ErlDrvSSizeT len = f->end;
ErlDrvSSizeT usize = ( (len+3) / 4 ) * 3;
ErlDrvBinary* bin;
uchar* inp;
uchar* outp;
int count = 0;
int n;
if ((bin = driver_alloc_binary(usize)) == NULL)
return error_reply(f, ENOMEM);
outp = (uchar*)bin->orig_bytes;
inp = (uchar*)f->buf;
while(len > 0) {
if ((n = uu_decode(*inp++)) < 0)
goto error;
len--;
if ((n == 0) && (*inp == '\n'))
break;
count += n; /* count characters */
while((n > 0) && (len >= 4)) {
int c1, c2, c3, c4;
c1 = uu_decode(inp[0]);
c2 = uu_decode(inp[1]);
c3 = uu_decode(inp[2]);
c4 = uu_decode(inp[3]);
inp += 4;
len -= 4;
switch(n) {
case 1:
*outp++ = (c1 << 2) | (c2 >> 4);
n = 0;
break;
case 2:
*outp++ = (c1 << 2) | (c2 >> 4);
*outp++ = (c2 << 4) | (c3 >> 2);
n = 0;
break;
default:
*outp++ = (c1 << 2) | (c2 >> 4);
*outp++ = (c2 << 4) | (c3 >> 2);
*outp++ = (c3 << 6) | c4;
n -= 3;
break;
}
}
if ((n != 0) || (*inp++ != '\n'))
goto error;
len--;
}
driver_free_binary(f->bin);
ram_file_set(f, bin, usize, count);
return numeric_reply(f, count);
error:
driver_free_binary(bin);
return error_reply(f, EINVAL);
}
static int ram_file_compress(RamFile *f)
{
ErlDrvSSizeT size = f->end;
ErlDrvBinary* bin;
if ((bin = erts_gzdeflate_buffer(f->buf, size)) == NULL) {
return error_reply(f, EINVAL);
}
driver_free_binary(f->bin);
size = bin->orig_size;
ram_file_set(f, bin, size, size);
return numeric_reply(f, size);
}
/* Tricky since we dont know the expanded size !!! */
/* First attempt is to double the size of input */
/* loop until we don't get Z_BUF_ERROR */
static int ram_file_uncompress(RamFile *f)
{
ErlDrvSSizeT size = f->end;
ErlDrvBinary* bin;
if ((bin = erts_gzinflate_buffer(f->buf, size)) == NULL) {
return error_reply(f, EINVAL);
}
driver_free_binary(f->bin);
size = bin->orig_size;
ram_file_set(f, bin, size, size);
return numeric_reply(f, size);
}
static void rfile_command(ErlDrvData e, char* buf, ErlDrvSizeT count)
{
RamFile* f = (RamFile*)e;
int error = 0;
ErlDrvBinary* bin;
char header[5]; /* result code + count */
ErlDrvSSizeT offset;
ErlDrvSSizeT origin; /* Origin of seek. */
ErlDrvSSizeT n;
count--;
switch(*(uchar*)buf++) {
case RAM_FILE_OPEN: /* args is initial data */
f->flags = get_int32(buf);
if (ram_file_init(f, buf+4, count-4, &error) < 0)
error_reply(f, error);
else
numeric_reply(f, 0); /* 0 is not used */
break;
case RAM_FILE_FDATASYNC:
if (f->flags == 0)
error_reply(f, EBADF);
else
reply(f, 1, 0);
break;
case RAM_FILE_FSYNC:
if (f->flags == 0)
error_reply(f, EBADF);
else
reply(f, 1, 0);
break;
case RAM_FILE_WRITE:
if (ram_file_write(f, buf, count, NULL, &error) < 0)
error_reply(f, error);
else
numeric_reply(f, count);
break;
case RAM_FILE_PWRITE:
if ((offset = get_int32(buf)) < 0)
error_reply(f, EINVAL);
else if (ram_file_write(f, buf+4, count-4, &offset, &error) < 0)
error_reply(f, error);
else
numeric_reply(f, count-4);
break;
case RAM_FILE_LSEEK:
offset = get_int32(buf);
origin = get_int32(buf+4);
if ((offset = ram_file_seek(f, offset, origin, &error)) < 0)
error_reply(f, error);
else
numeric_reply(f, offset);
break;
case RAM_FILE_PREAD:
if ((offset = get_int32(buf)) < 0) {
error_reply(f, EINVAL);
break;
}
count = get_int32(buf+4);
if ((n = ram_file_read(f, count, &bin, &offset, &error)) < 0) {
error_reply(f, error);
} else {
header[0] = RAM_FILE_RESP_DATA;
put_int32(n, header+1);
driver_output_binary(f->port, header, sizeof(header),
bin, 0, n);
driver_free_binary(bin);
}
break;
case RAM_FILE_READ:
count = get_int32(buf);
if ((n = ram_file_read(f, count, &bin, NULL, &error)) < 0)
error_reply(f, error);
else {
header[0] = RAM_FILE_RESP_DATA;
put_int32(n, header+1);
driver_output_binary(f->port, header, sizeof(header),
bin, 0, n);
driver_free_binary(bin);
}
break;
case RAM_FILE_TRUNCATE:
if (!(f->flags & RAM_FILE_MODE_WRITE)) {
error_reply(f, EACCES);
break;
}
if (f->end > f->cur)
sys_memzero(f->buf + f->cur, f->end - f->cur);
f->end = f->cur;
reply(f, 1, 0);
break;
case RAM_FILE_GET: /* return a copy of the file */
n = f->end; /* length */
if ((bin = driver_alloc_binary(n)) == NULL) {
error_reply(f, ENOMEM);
break;
}
sys_memcpy(bin->orig_bytes, f->buf, n);
header[0] = RAM_FILE_RESP_DATA;
put_int32(n, header+1);
driver_output_binary(f->port, header, sizeof(header),
bin, 0, n);
driver_free_binary(bin);
break;
case RAM_FILE_GET_CLOSE: /* return the file and close driver */
n = f->end; /* length */
bin = f->bin;
f->bin = NULL; /* NUKE IT */
header[0] = RAM_FILE_RESP_DATA;
put_int32(n, header+1);
driver_output_binary(f->port, header, sizeof(header),
bin, 0, n);
driver_free_binary(bin);
driver_failure(f->port, 0);
break;
case RAM_FILE_SIZE:
numeric_reply(f, f->end);
break;
case RAM_FILE_SET: /* re-init file with new data */
if ((n = ram_file_init(f, buf, count, &error)) < 0)
error_reply(f, error);
else
numeric_reply(f, n); /* 0 is not used */
break;
case RAM_FILE_COMPRESS: /* inline compress the file */
ram_file_compress(f);
break;
case RAM_FILE_UNCOMPRESS: /* inline uncompress file */
ram_file_uncompress(f);
break;
case RAM_FILE_UUENCODE: /* uuencode file */
ram_file_uuencode(f);
break;
case RAM_FILE_UUDECODE: /* uudecode file */
ram_file_uudecode(f);
break;
case RAM_FILE_ADVISE:
if (f->flags == 0)
error_reply(f, EBADF);
else
reply(f, 1, 0);
break;
}
/*
* Ignore anything else -- let the caller hang.
*/
}