I'm trying to send .amr files from my desktop to a SIM900 GSM module over UART.
I'm using teuniz's RS232 library.
I do the initialisation using AT commands and then read the file into a buffer and write it to the UART using the RS232_SendByte() library function byte-by-byte, but it doesn't seem to work.
I send the following AT commands:
AT+CFSINIT
AT+CFSWFILE=\"audio.amr\",0,6694,13000 # After which I get the CONNECT message from the SIM900 module
# Here's where I send the file
AT+CFSGFIS=\"audio.amr\"
Here's my code:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "rs232.h"
char *readFile(char *filename, int *size) {
char *source = NULL;
FILE *fp = fopen(filename, "rb");
if (fp != NULL) {
/* Go to the end of the file. */
if (fseek(fp, 0L, SEEK_END) == 0) {
/* Get the size of the file. */
long bufsize = ftell(fp);
if (bufsize == -1) { return NULL; }
/* Allocate our buffer to that size. */
source = malloc(sizeof(char) * (bufsize + 1));
if(!source) return NULL;
/* Go back to the start of the file. */
if (fseek(fp, 0L, SEEK_SET) != 0) { return NULL; }
/* Read the entire file into memory. */
size_t newLen = fread(source, sizeof(char), bufsize, fp);
if ( ferror( fp ) != 0 ) {
fputs("Error reading file", stderr);
free(source);
return NULL;
} else {
source[newLen++] = 0; /* Just to be safe. */
}
*size = bufsize;
}
fclose(fp);
}
return source;
}
int main(int argc, char *argv[])
{
int ret = 0, cport_nr = 2, bdrate=38400;
char data[2000] = {0};
if(RS232_OpenComport(cport_nr, bdrate)) {
printf("Can not open comport\n");
ret = -1;
goto END;
}
int size;
unsigned char *filebuf = readFile("audio.amr", &size);
if (!filebuf) {
ret = -1;
goto END_1;
}
/* Initialization */
RS232_cputs(cport_nr, "AT");
RS232_cputs(cport_nr, "AT+CFSINIT");
sleep(1);
RS232_cputs(cport_nr, "AT+CFSWFILE=\"audio.amr\",0,6694,13000");
/* Wait for CONNECT */
sleep(2);
printf("Sending file of size: %d\n", size);
int i;
for (i = 0; i < size; ++i) {
putchar(filebuf[i]);
RS232_SendByte(cport_nr, filebuf[i]);
}
free(filebuf);
sleep(1);
/* Check if file transferred right */
RS232_cputs(cport_nr, "AT+CFSGFIS=\"audio.amr\"");
END_1:
RS232_CloseComport(cport_nr);
END:
return ret;
}
EDIT 1
Normally, the procedure to send a file to SIM900 using AT commands would be as documented here:
AT+CFSINIT # Initialize flash; Response is OK
AT+CFSWFILE=<filename>,<writeMode>,<fileSize>,<InputTime> # Write file with these parameter; Response is CONNECT; So this is when I start sending the file
Here's where I send the file. If it worked and the sent file size matched the <filesize> sent in the above command, SIM900 must respond with OK, which it doesn't. :(
AT+CFSGFIS=<filename> # Gives the file size on flash. This gives me an error since the file didn't upload correctly.
This leads me to beleive there's something wrong with my program. I'm reading the file in binary mode. And the size reported is exacty the same as I specify in the AT+CFSWFILE=<filename>,<writeMode>,<fileSize>,<InputTime> command.
Related
I am trying to implement a functionality similar to objcopy where bytes of a binary file (specifically the .text section) will be printed out using open() and read(). How would I set the buffer sizes and iterate till the end of a .text section so that I don't read more bytes than I have to in order to avoid errors?
Here is how you read a file using open() and read().
P.S I used fopen() and fread() instead of open() and read() because I am currently working with a Windows machine. However, the results will be the same for either.
int main()
{
FILE *file = fopen("input.txt", "r");
char buffer[2048];
if (file)
{
/* Loop will continue until an end of file is reached i.e. fread returns 0 elements read */
while (fread(buffer, 4, 1, file) == 1)
{
printf("%s", buffer);
}
fclose(file);
}
}
Update: For interpreting ELF files specifically, I would recommend taking a look at the following resources:
Check out the following code snippet. It shows how you can interpret an ELF file.
#include <stdio.h>
#include <libelf.h>
#include <stdlib.h>
#include <string.h>
static void failure(void);
void main(int argc, char **argv)
{
Elf32_Shdr *shdr;
Elf32_Ehdr *ehdr;
Elf *elf;
Elf_Scn *scn;
Elf_Data *data;
int fd;
unsigned int cnt;
/* Open the input file */
if ((fd = open(argv[1], O_RDONLY)) == -1)
exit(1);
/* Obtain the ELF descriptor */
(void)elf_version(EV_CURRENT);
if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)
failure();
/* Obtain the .shstrtab data buffer */
if (((ehdr = elf32_getehdr(elf)) == NULL) ||
((scn = elf_getscn(elf, ehdr->e_shstrndx)) == NULL) ||
((data = elf_getdata(scn, NULL)) == NULL))
failure();
/* Traverse input filename, printing each section */
for (cnt = 1, scn = NULL; scn = elf_nextscn(elf, scn); cnt++)
{
if ((shdr = elf32_getshdr(scn)) == NULL)
failure();
(void)printf("[%d] %s\n", cnt,
(char *)data->d_buf + shdr->sh_name);
}
} /* end main */
static void
failure()
{
(void)fprintf(stderr, "%s\n", elf_errmsg(elf_errno()));
exit(1);
}
I would also recommend checking out the elfutils library, which can be found here.
What I'm trying to do is read this text from a file:
Tomorrow, and tomorrow, and tomorrow,
Creeps in this petty pace from day to day,
To the last syllable of recorded time;
And all our yesterdays have lighted fools
The way to dusty death. Out, out, brief candle!
Life's but a walking shadow, a poor player
That struts and frets his hour upon the stage
And then is heard no more. It is a tale
Told by an idiot, full of sound and fury
Signifying nothing.
And change it all to upper case letters.
I can do this if I read the text from one file and print to another.
#include <stdio.h>
#include <ctype.h>
int main() {
FILE* input_file = fopen("some_text.txt", "a+");
char c = fgetc(input_file);
int charc = 0;
int alpha = 0;
while(1){
if(isalpha(c)){
alpha = alpha + 1;
}
charc = charc + 1;
fprintf(input_file,"%c",toupper(c));
c = fgetc(input_file);
if(feof(input_file)){
break;
}
}
fprintf(input_file,"\nTotal Characters: %d, Total alphabetical characters: %d",charc,alpha);
fclose(input_file);
return 0;
}
If you are simply wanting to convert all characters in a file to uppercase and write the results back to the same file, the direct approach is to open the file for reading, get the length of the file and allocate a buffer to hold the entire file and read the entire file into the buffer, and close the file. Then loop over each character in the buffer calling toupper() on each character and converting the buffer to all uppercase. Then open the file again for writing which will truncate the file and then write the entire buffer back out to the file closing the file and freeing the buffer when you are done.
A short example taking the filename to convert as the first argument could be:
Open File in "r" (Read) Mode
...
int main (int argc, char **argv) {
char *filebuf = NULL;
long fplen = 0;
FILE *fp = NULL;
if (argc < 2) { /* validate argument given for filename */
fprintf (stderr, "usage: %s filename\n", argv[0]);
return 1;
}
if (!(fp = fopen (argv[1], "r"))) { /* open/validate file open for read */
perror ("fopen-read");
return 1;
}
Determine File Length
fseek (fp, 0, SEEK_END); /* seek end of file */
if ((fplen = ftell (fp)) == -1) { /* get file length */
perror ("ftell-length");
return 1;
}
fseek (fp, 0, SEEK_SET); /* seek beginning */
Allocate Storage for filebuf
/* allocate memory for file */
if (!(filebuf = malloc (fplen * sizeof *filebuf))) {
perror ("malloc-filebuf");
return 1;
}
Read Entire File Into filebuf & Close File
/* read file into filebuf */
if (fread (filebuf, 1, fplen, fp) != (size_t)fplen) {
perror ("fread-filebuf");
return 1;
}
fclose (fp); /* close file after read */
Convert filebuf to UpperCase
for (long i = 0; i < fplen; i++) /* convert all chars toupper */
filebuf[i] = toupper(filebuf[i]);
Open File for Writing "w" Mode & Write filebuf to File & Close
if (!(fp = fopen (argv[1], "w"))) { /* open/validate file open for write */
perror ("fopen-write");
return 1;
}
/* write filebuf to file */
if (fwrite (filebuf, 1, fplen, fp) != (size_t)fplen) {
perror ("fwrite-filebuf");
return 1;
}
if (fclose (fp) == EOF) /* validate close-after-write */
perror ("fclose_after-write");
(note: you always validate close-after-write to catch any error associated with flushing the stream that would not be caught on your validation of fwrite)
Free filebuf Memory
free (filebuf);
That is it in a nutshell. Putting it altogether you could do:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>
int main (int argc, char **argv) {
char *filebuf = NULL;
long fplen = 0;
FILE *fp = NULL;
if (argc < 2) { /* validate argument given for filename */
fprintf (stderr, "usage: %s filename\n", argv[0]);
return 1;
}
if (!(fp = fopen (argv[1], "r"))) { /* open/validate file open for read */
perror ("fopen-read");
return 1;
}
fseek (fp, 0, SEEK_END); /* seek end of file */
if ((fplen = ftell (fp)) == -1) { /* get file length */
perror ("ftell-length");
return 1;
}
fseek (fp, 0, SEEK_SET); /* seek beginning */
/* allocate memory for file */
if (!(filebuf = malloc (fplen * sizeof *filebuf))) {
perror ("malloc-filebuf");
return 1;
}
/* read file into filebuf */
if (fread (filebuf, 1, fplen, fp) != (size_t)fplen) {
perror ("fread-filebuf");
return 1;
}
fclose (fp); /* close file after read */
for (long i = 0; i < fplen; i++) /* convert all chars toupper */
filebuf[i] = toupper(filebuf[i]);
if (!(fp = fopen (argv[1], "w"))) { /* open/validate file open for write */
perror ("fopen-write");
return 1;
}
/* write filebuf to file */
if (fwrite (filebuf, 1, fplen, fp) != (size_t)fplen) {
perror ("fwrite-filebuf");
return 1;
}
if (fclose (fp) == EOF) /* validate close-after-write */
perror ("fclose_after-write");
free (filebuf);
}
Example Input File
$ cat dat/cj2upper.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.
Example Use
$ ./bin/fread_file_toupper dat/cj2upper.txt
Resulting Output File
$ cat dat/cj2upper.txt
THIS IS A TALE
OF CAPTAIN JACK SPARROW
A PIRATE SO BRAVE
ON THE SEVEN SEAS.
Look things over and let me know if you have questions. There is more than one way to do this, but this is likely one of the more direct routes.
Almost right, here are some changes to make it work
First open the file in binary mode, this allows you to move in the file with fseek. the rb+ means you can read/write to the file.
Added some error handling if file is not there, always have error handling.
When you write back to the disk, make sure to flush it out to disk since fgetc et al work on a buffer.
fputc is better in this case, since you are anyway just writing one char.
#include <stdio.h>
#include <ctype.h>
int main()
{
char filename[] = "some_text.txt";
FILE* input_file = fopen(filename, "rb+"); // open in binary mode
if ( input_file == NULL ) // some error handling
{
perror(filename);
return -1;
}
int c = fgetc(input_file); // return value is int
int charc = 0;
int alpha = 0;
do
{
if(isalpha(c))
{
alpha = alpha + 1;
}
charc = charc + 1;
// if alpha, then go back one character and rewrite it
if(isalpha(c))
{
fseek(input_file, -1, SEEK_CUR);
fputc(toupper(c),input_file); // use fputc instead of fprintf
fflush(input_file);
}
// read next
c = fgetc(input_file);
}
while (c != EOF);
// at end of file, add some more
fprintf(input_file,"\nTotal Characters: %d, Total alphabetical characters: %d",charc,alpha);
fclose(input_file);
return 0;
}
For performance reason, assuming that most characters will need conversion, make sense to leverage STDIO, which will leverage application level buffering
Example eliminate error checking for readability.
main(...)
{
// Open file
FILE *fp = ... ;
const int BUFFER_SIZE = 1024 ;
char buff[BUFFER_SIZE] ;
while ( 1 ) {
// Read
long loc = ftell(fp) ;
n = fread(buff, 1, sizeof(buff), fp) ;
if ( n <= 0 ) break ;
// Convert
int do_update = 0 ;
for (int i=0 ; i<n; i++ ) {
if ( islower(buff[i] ) {
buff[i] = toupper(buff[i])
do_update = 1 ;
} ;
if(isalpha(c)){
alpha = alpha + 1;
}
charc = charc + 1;
} ;
// Update, only if anything changed
if ( do_update ) {
long next = ftell(fp) ;
fseek(fp, loc, SEEK_SET) ;
fwrite(buff, 1, n, fp) ;
fseek(fp, next, SEEK_SET) ;
} ;
} ;
}
On Ubuntu Linux I have written a c program based on the Libao example program to open audio wave file and play. It works fine but at the end after finish playing there is crackling high pitch noise. Here is the code which I modified mt libao example. How can I fix it? Please help
#include <stdio.h>
#include <string.h>
#include <ao/ao.h>
#include <math.h>
#define BUF_SIZE 4096
int main(int argc, char **argv)
{
ao_device *device;
ao_sample_format format;
int default_driver;
char *buffer;
int buf_size;
int sample;
FILE *fp;
float freq = 440.0;
int i;
/* -- Initialize -- */
fprintf(stderr, "libao example program\n");
ao_initialize();
/* -- Setup for default driver -- */
default_driver = ao_default_driver_id();
memset(&format, 0, sizeof(format));
format.bits = 16;
format.channels = 2;
format.rate = 44100;
format.byte_format = AO_FMT_LITTLE;
/* -- Open driver -- */
// device = ao_open_live(default_driver, &format, NULL /* no options */);
device = ao_open_live(default_driver, &format, NULL /* no options */);
if (device == NULL) {
fprintf(stderr, "Error opening device.\n");
return 1;
}
fp = fopen("nc.wav", "rb");
if (fp == NULL) {
fprintf(stderr, "Unable to open file \n");
return;
}
fseek(fp, 0, SEEK_END);
unsigned long fileLen = ftell(fp);
fseek(fp, 0, SEEK_SET);
//Allocate memory
buffer=(char *)malloc(fileLen+1);
if (!buffer)
{
fprintf(stderr, "Memory error!");
fclose(fp);
return;
}
fread(buffer, fileLen, 1, fp);
fclose(fp);
ao_play(device, buffer, buf_size);
/* -- Close and shutdown -- */
ao_close(device);
ao_shutdown();
return (0);
}
The buf_size variable is passed to ao_play without being initialized, and the crackles most likely occur because it is playing past the end of the sample buffer into random memory.
Depending on your compiler settings, the compiler can warn you about uninitialized variables bugs like this (gcc only does it when optimizations are turned on, via the -Wuninitialized or -Wall settings).
i have been trying to decode an MP3 file to pcm, using ffmpeg API, but i keep getting an error
[mp3 # 0x8553020]Header missing
this is the code i use :
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_AV_CONFIG_H
#undef HAVE_AV_CONFIG_H
#endif
#include "libavcodec/avcodec.h"
#include "libavutil/mathematics.h"
#define INBUF_SIZE 4096
#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096
static void audio_decode_example(const char *outfilename, const char *filename)
{
AVCodec *codec;
AVCodecContext *c= NULL;
int out_size, len;
FILE *f, *outfile;
uint8_t *outbuf;
uint8_t inbuf[AUDIO_INBUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
AVPacket avpkt;
av_init_packet(&avpkt);
printf("Audio decoding\n");
/* find the mpeg audio decoder */
codec = avcodec_find_decoder(CODEC_ID_MP3ON4);
if (!codec) {
fprintf(stderr, "codec not found\n");
exit(1);
}
c= avcodec_alloc_context();
/* open it */
if (avcodec_open(c, codec) < 0) {
fprintf(stderr, "could not open codec\n");
exit(1);
}
outbuf = malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE);
f = fopen(filename, "rb");
if (!f) {
fprintf(stderr, "could not open %s\n", filename);
exit(1);
}
outfile = fopen(outfilename, "wb");
if (!outfile) {
av_free(c);
exit(1);
}
/* decode until eof */
avpkt.data = inbuf;
avpkt.size = fread(inbuf, 1, AUDIO_INBUF_SIZE, f);
while (avpkt.size > 0) {
out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
len = avcodec_decode_audio3(c, (short *)outbuf, &out_size, &avpkt);
if (len < 0) {
fprintf(stderr, "Error while decoding\n");
exit(1);
}
if (out_size > 0) {
/* if a frame has been decoded, output it */
fwrite(outbuf, 1, out_size, outfile);
}
avpkt.size -= len;
avpkt.data += len;
if (avpkt.size < AUDIO_REFILL_THRESH) {
/* Refill the input buffer, to avoid trying to decode
* incomplete frames. Instead of this, one could also use
* a parser, or use a proper container format through
* libavformat. */
memmove(inbuf, avpkt.data, avpkt.size);
avpkt.data = inbuf;
len = fread(avpkt.data + avpkt.size, 1,
AUDIO_INBUF_SIZE - avpkt.size, f);
if (len > 0)
avpkt.size += len;
}
}
fclose(outfile);
fclose(f);
free(outbuf);
avcodec_close(c);
av_free(c);
}
int main(int argc, char **argv)
{
const char *filename;
/* must be called before using avcodec lib */
avcodec_init();
/* register all the codecs */
avcodec_register_all();
audio_decode_example("test.wav", argv[1]);
return 0;
}
when i use the same code to directly play the sound, like this :
if (out_size > 0) {
/* if a frame has been decoded, output it *
play_sound(outbuf, out_size);
}
i have no problem at all with some files, other mp3 files just gives an error without even starting ... is there any ideas ?
PS: this code is from libavcodec/api-example.c , modified as needed
I think i found my answer, the avpkt.data must have a header in front, without any garbage or previous frame bytes, or may be initial mp3 file data (name, gender, year ... etc).
so a little parser must be wrote, this is a useful link for mp3 headers (just search for the correct bytes in within the file, and increase avpkt.data pointer to match):
http://www.mp3-tech.org/programmer/frame_header.html
Use avformat for reading instead of fread(). For example, it can be customized (e.g. for buffering), it can detect and check formats automatically on opening and also has separated probe functions and other format-related stuff. And it works properly with headers. I came to following usage (warning, code can contain errors)
struct FormatCtx {
inline FormatCtx(const char* filename)
: ctx_(avformat_alloc_context()) {
av_init_packet(&p);
if (avformat_open_input(&ctx_, filename, 0, 0) < 0)
abort();
if (avformat_find_stream_info(ctx_, 0) < 0)
abort();
}
inline ~FormatCtx() {
av_free_packet(&p);
}
inline bool read() {
return av_read_frame(ctx_, &p) >= 0;
}
AVFormatContext* ctx_;
AVPacket p;
} formatCtx_;
AVCodec* findCodec(const char* filename) {
AVCodec* codec = formatCtx_.ctx_->audio_codec;
if (codec)
return codec;
codec = avcodec_find_decoder(formatCtx_.ctx_->audio_codec_id);
if (codec)
return codec;
AVOutputFormat* fmt = av_guess_format(0, //const char *short_name,
filename, 0); // const char *mime_type);;
codec = fmt ? avcodec_find_decoder(fmt->audio_codec) : 0;
if (codec)
return codec;
return 0;
}
//*** initialize all stuff ***
AVCodec* codec = findCodec(filename);
if (!codec)
exit(1);
AVCodecContext* c; // class member for me, needed for following reading
int stream_index_; // class member for me, needed for extra stuff
for (size_t i = 0; i < formatCtx_.ctx_->nb_streams; ++i) {
AVCodecContext* tc = formatCtx_.ctx_->streams[i]->codec;
if (tc->codec_type == AVMEDIA_TYPE_AUDIO) {
c = tc;
stream_index_ = i;
break;
}
}
// for example, here we're know track length
l->onDurationDetected(double(formatCtx_.ctx_->streams[stream_index_]->duration)
* av_q2d(formatCtx_.ctx_->streams[stream_index_]->time_base));
if (avcodec_open2(c, codec, &d.d_) < 0)
exit(1);
c->channels = 2;
c->sample_rate = 48000;
c->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
c->channel_layout = av_get_default_channel_layout(2);
After that you should basically prepare decoded_frame from TC's example and pass packet used for reading to avcodec_decode_audio4 (instead of avpkt).
Is fopen("tftp://1.1.1.1/file.txt","rb"); a valid statement? Can urls be opened using fopen in C programming?
No, but you can use libcurl, an example:
#include <stdio.h>
#include <curl/curl.h>
/*
* This is an example showing how to get a single file from an FTP server.
* It delays the actual destination file creation until the first write
* callback so that it won't create an empty file in case the remote file
* doesn't exist or something else fails.
*/
struct FtpFile {
const char *filename;
FILE *stream;
};
static size_t my_fwrite(void *buffer, size_t size, size_t nmemb, void *stream)
{
struct FtpFile *out=(struct FtpFile *)stream;
if(out && !out->stream) {
/* open file for writing */
out->stream=fopen(out->filename, "wb");
if(!out->stream)
return -1; /* failure, can't open file to write */
}
return fwrite(buffer, size, nmemb, out->stream);
}
int main(void)
{
CURL *curl;
CURLcode res;
struct FtpFile ftpfile={
"curl.tar.gz", /* name to store the file as if succesful */
NULL
};
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
/*
* You better replace the URL with one that works!
*/
curl_easy_setopt(curl, CURLOPT_URL,
"ftp://ftp.example.com/pub/www/utilities/curl/curl-7.9.2.tar.gz");
/* Define our callback to get called when there's data to be written */
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_fwrite);
/* Set a pointer to our struct to pass to the callback */
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ftpfile);
/* Switch on full protocol/debug output */
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
res = curl_easy_perform(curl);
/* always cleanup */
curl_easy_cleanup(curl);
if(CURLE_OK != res) {
/* we failed */
fprintf(stderr, "curl told us %d\n", res);
}
}
if(ftpfile.stream)
fclose(ftpfile.stream); /* close the local file */
curl_global_cleanup();
return 0;
}
Or (as pointed out by #Paul) you can pipe a process (E.g.: wget url) with popen:
#include <stdio.h>
FILE *popen(const char *command, const char *mode);
int pclose(FILE *stream);
int main(void)
{
/* wget -q = silent mode */
FILE *cmd = popen("wget -q -O - ftp://debian.org/debian-security/README.security", "r");
char result[1024];
while (fgets(result, sizeof(result), cmd) != NULL)
printf("%s", result);
pclose(cmd);
return 0;
}
The fopen in <stdio.h> doesn't do that.
However, nothing prevents someone from writing a function called fopen() that does something else.
FILE *popen(const char *command, const char *mode) can be used to spawn a process running an appropriate command line tool such as tftp or wget, and thereby accomplish this downloading of a remote resource into a file descriptor accessible from C code. The syntax for a popen() call is very similar to what you have shown. It is missing the program name for the download utility, though. A bare url or ftp address won't work for popen().
See:
fopen man page
popen man page
Also note:
The PHP language version of fopen() does open bare URLs. But PHP != C
It's not that easy as simply using fopen but it can be done.
You need to use libcurl. Take a look here.
From the site:
/*****************************************************************************
*
* This example requires libcurl 7.9.7 or later.
*/
#include <stdio.h>
#include <string.h>
#ifndef WIN32
#include <sys/time.h>
#endif
#include <stdlib.h>
#include <errno.h>
#include <curl/curl.h>
enum fcurl_type_e {
CFTYPE_NONE=0,
CFTYPE_FILE=1,
CFTYPE_CURL=2
};
struct fcurl_data
{
enum fcurl_type_e type; /* type of handle */
union {
CURL *curl;
FILE *file;
} handle; /* handle */
char *buffer; /* buffer to store cached data*/
size_t buffer_len; /* currently allocated buffers length */
size_t buffer_pos; /* end of data in buffer*/
int still_running; /* Is background url fetch still in progress */
};
typedef struct fcurl_data URL_FILE;
/* exported functions */
URL_FILE *url_fopen(const char *url,const char *operation);
int url_fclose(URL_FILE *file);
int url_feof(URL_FILE *file);
size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file);
char * url_fgets(char *ptr, size_t size, URL_FILE *file);
void url_rewind(URL_FILE *file);
/* we use a global one for convenience */
CURLM *multi_handle;
/* curl calls this routine to get more data */
static size_t write_callback(char *buffer,
size_t size,
size_t nitems,
void *userp)
{
char *newbuff;
size_t rembuff;
URL_FILE *url = (URL_FILE *)userp;
size *= nitems;
rembuff=url->buffer_len - url->buffer_pos; /* remaining space in buffer */
if(size > rembuff) {
/* not enough space in buffer */
newbuff=realloc(url->buffer,url->buffer_len + (size - rembuff));
if(newbuff==NULL) {
fprintf(stderr,"callback buffer grow failed\n");
size=rembuff;
}
else {
/* realloc suceeded increase buffer size*/
url->buffer_len+=size - rembuff;
url->buffer=newbuff;
}
}
memcpy(&url->buffer[url->buffer_pos], buffer, size);
url->buffer_pos += size;
return size;
}
/* use to attempt to fill the read buffer up to requested number of bytes */
static int fill_buffer(URL_FILE *file, size_t want)
{
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
struct timeval timeout;
int rc;
/* only attempt to fill buffer if transactions still running and buffer
* doesnt exceed required size already
*/
if((!file->still_running) || (file->buffer_pos > want))
return 0;
/* attempt to fill buffer */
do {
int maxfd = -1;
long curl_timeo = -1;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);
/* set a suitable timeout to fail on */
timeout.tv_sec = 60; /* 1 minute */
timeout.tv_usec = 0;
curl_multi_timeout(multi_handle, &curl_timeo);
if(curl_timeo >= 0) {
timeout.tv_sec = curl_timeo / 1000;
if(timeout.tv_sec > 1)
timeout.tv_sec = 1;
else
timeout.tv_usec = (curl_timeo % 1000) * 1000;
}
/* get file descriptors from the transfers */
curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
/* In a real-world program you OF COURSE check the return code of the
function calls. On success, the value of maxfd is guaranteed to be
greater or equal than -1. We call select(maxfd + 1, ...), specially
in case of (maxfd == -1), we call select(0, ...), which is basically
equal to sleep. */
rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
switch(rc) {
case -1:
/* select error */
break;
case 0:
default:
/* timeout or readable/writable sockets */
curl_multi_perform(multi_handle, &file->still_running);
break;
}
} while(file->still_running && (file->buffer_pos < want));
return 1;
}
/* use to remove want bytes from the front of a files buffer */
static int use_buffer(URL_FILE *file,int want)
{
/* sort out buffer */
if((file->buffer_pos - want) <=0) {
/* ditch buffer - write will recreate */
if(file->buffer)
free(file->buffer);
file->buffer=NULL;
file->buffer_pos=0;
file->buffer_len=0;
}
else {
/* move rest down make it available for later */
memmove(file->buffer,
&file->buffer[want],
(file->buffer_pos - want));
file->buffer_pos -= want;
}
return 0;
}
URL_FILE *url_fopen(const char *url,const char *operation)
{
/* this code could check for URLs or types in the 'url' and
basicly use the real fopen() for standard files */
URL_FILE *file;
(void)operation;
file = malloc(sizeof(URL_FILE));
if(!file)
return NULL;
memset(file, 0, sizeof(URL_FILE));
if((file->handle.file=fopen(url,operation)))
file->type = CFTYPE_FILE; /* marked as URL */
else {
file->type = CFTYPE_CURL; /* marked as URL */
file->handle.curl = curl_easy_init();
curl_easy_setopt(file->handle.curl, CURLOPT_URL, url);
curl_easy_setopt(file->handle.curl, CURLOPT_WRITEDATA, file);
curl_easy_setopt(file->handle.curl, CURLOPT_VERBOSE, 0L);
curl_easy_setopt(file->handle.curl, CURLOPT_WRITEFUNCTION, write_callback);
if(!multi_handle)
multi_handle = curl_multi_init();
curl_multi_add_handle(multi_handle, file->handle.curl);
/* lets start the fetch */
curl_multi_perform(multi_handle, &file->still_running);
if((file->buffer_pos == 0) && (!file->still_running)) {
/* if still_running is 0 now, we should return NULL */
/* make sure the easy handle is not in the multi handle anymore */
curl_multi_remove_handle(multi_handle, file->handle.curl);
/* cleanup */
curl_easy_cleanup(file->handle.curl);
free(file);
file = NULL;
}
}
return file;
}
int url_fclose(URL_FILE *file)
{
int ret=0;/* default is good return */
switch(file->type) {
case CFTYPE_FILE:
ret=fclose(file->handle.file); /* passthrough */
break;
case CFTYPE_CURL:
/* make sure the easy handle is not in the multi handle anymore */
curl_multi_remove_handle(multi_handle, file->handle.curl);
/* cleanup */
curl_easy_cleanup(file->handle.curl);
break;
default: /* unknown or supported type - oh dear */
ret=EOF;
errno=EBADF;
break;
}
if(file->buffer)
free(file->buffer);/* free any allocated buffer space */
free(file);
return ret;
}
int url_feof(URL_FILE *file)
{
int ret=0;
switch(file->type) {
case CFTYPE_FILE:
ret=feof(file->handle.file);
break;
case CFTYPE_CURL:
if((file->buffer_pos == 0) && (!file->still_running))
ret = 1;
break;
default: /* unknown or supported type - oh dear */
ret=-1;
errno=EBADF;
break;
}
return ret;
}
size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file)
{
size_t want;
switch(file->type) {
case CFTYPE_FILE:
want=fread(ptr,size,nmemb,file->handle.file);
break;
case CFTYPE_CURL:
want = nmemb * size;
fill_buffer(file,want);
/* check if theres data in the buffer - if not fill_buffer()
* either errored or EOF */
if(!file->buffer_pos)
return 0;
/* ensure only available data is considered */
if(file->buffer_pos < want)
want = file->buffer_pos;
/* xfer data to caller */
memcpy(ptr, file->buffer, want);
use_buffer(file,want);
want = want / size; /* number of items */
break;
default: /* unknown or supported type - oh dear */
want=0;
errno=EBADF;
break;
}
return want;
}
char *url_fgets(char *ptr, size_t size, URL_FILE *file)
{
size_t want = size - 1;/* always need to leave room for zero termination */
size_t loop;
switch(file->type) {
case CFTYPE_FILE:
ptr = fgets(ptr,size,file->handle.file);
break;
case CFTYPE_CURL:
fill_buffer(file,want);
/* check if theres data in the buffer - if not fill either errored or
* EOF */
if(!file->buffer_pos)
return NULL;
/* ensure only available data is considered */
if(file->buffer_pos < want)
want = file->buffer_pos;
/*buffer contains data */
/* look for newline or eof */
for(loop=0;loop < want;loop++) {
if(file->buffer[loop] == '\n') {
want=loop+1;/* include newline */
break;
}
}
/* xfer data to caller */
memcpy(ptr, file->buffer, want);
ptr[want]=0;/* allways null terminate */
use_buffer(file,want);
break;
default: /* unknown or supported type - oh dear */
ptr=NULL;
errno=EBADF;
break;
}
return ptr;/*success */
}
void url_rewind(URL_FILE *file)
{
switch(file->type) {
case CFTYPE_FILE:
rewind(file->handle.file); /* passthrough */
break;
case CFTYPE_CURL:
/* halt transaction */
curl_multi_remove_handle(multi_handle, file->handle.curl);
/* restart */
curl_multi_add_handle(multi_handle, file->handle.curl);
/* ditch buffer - write will recreate - resets stream pos*/
if(file->buffer)
free(file->buffer);
file->buffer=NULL;
file->buffer_pos=0;
file->buffer_len=0;
break;
default: /* unknown or supported type - oh dear */
break;
}
}
/* Small main program to retrive from a url using fgets and fread saving the
* output to two test files (note the fgets method will corrupt binary files if
* they contain 0 chars */
int main(int argc, char *argv[])
{
URL_FILE *handle;
FILE *outf;
int nread;
char buffer[256];
const char *url;
if(argc < 2)
url="http://192.168.7.3/testfile";/* default to testurl */
else
url=argv[1];/* use passed url */
/* copy from url line by line with fgets */
outf=fopen("fgets.test","w+");
if(!outf) {
perror("couldn't open fgets output file\n");
return 1;
}
handle = url_fopen(url, "r");
if(!handle) {
printf("couldn't url_fopen() %s\n", url);
fclose(outf);
return 2;
}
while(!url_feof(handle)) {
url_fgets(buffer,sizeof(buffer),handle);
fwrite(buffer,1,strlen(buffer),outf);
}
url_fclose(handle);
fclose(outf);
/* Copy from url with fread */
outf=fopen("fread.test","w+");
if(!outf) {
perror("couldn't open fread output file\n");
return 1;
}
handle = url_fopen("testfile", "r");
if(!handle) {
printf("couldn't url_fopen() testfile\n");
fclose(outf);
return 2;
}
do {
nread = url_fread(buffer, 1,sizeof(buffer), handle);
fwrite(buffer,1,nread,outf);
} while(nread);
url_fclose(handle);
fclose(outf);
/* Test rewind */
outf=fopen("rewind.test","w+");
if(!outf) {
perror("couldn't open fread output file\n");
return 1;
}
handle = url_fopen("testfile", "r");
if(!handle) {
printf("couldn't url_fopen() testfile\n");
fclose(outf);
return 2;
}
nread = url_fread(buffer, 1,sizeof(buffer), handle);
fwrite(buffer,1,nread,outf);
url_rewind(handle);
buffer[0]='\n';
fwrite(buffer,1,1,outf);
nread = url_fread(buffer, 1,sizeof(buffer), handle);
fwrite(buffer,1,nread,outf);
url_fclose(handle);
fclose(outf);
return 0;/* all done */
}