Thread: file i/o and plain old bad luck

  1. #1
    Registered User
    Join Date
    Feb 2006
    Posts
    16

    file i/o and plain old bad luck

    Greetings I am a newbie, but a newbie with experience in other languages. This does not, however, usually detain me from being an idiot.

    So, right now as a jumping off point in C I have decided I'd try to make a crappy webserver. Right now it serves files from static GET requests, and I want it to now write its pid in a file so that I might control it (it's a daemon, can't be good to keep closing the shell window to stop it).

    Nonetheless, it is supposed to be a quick tutorial in file i/o. Here is my situation:

    The server serves files just fine, but it does not do any file writing. Now that I say this, let me say it differently: on Ubuntu, it does not do any writing. My friend compiled it perfectly on MacOS with Xcode, but I am experiencing problems with file i/o on Ubuntu (but it does serve files). To further the insult, I tried running the program on my Slackware box, and the damndest thing -- it doesn't even serve files there.

    Here is my code:

    Code:
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    
    /* Arg format: 1=port */
    
    #define BUFSIZE 8196
    
    struct {
    	char *ext;
    	char *filetype;
    } extensions [] = {
    	{"gif","image/gif"},
    	{"jpg","image/jpeg"},
    	{"jpeg","image/jpeg"},
    	{"png","image/png"},
    	{"zip","image/zip"},
    	{"gz","image/gz"},
    	{"tar","image/tar"},
    	{"htm","text/html"},
    	{"html","text/html"},
    	{"c","text/text"},
    	{0,0}
    };
    
    void writeFile(char *filename, char *toWrite)
    {
    	FILE * thefile;
    	thefile = fopen(filename,"wb");
    	fwrite(toWrite,sizeof toWrite, 1, thefile);
    	(void)fclose(thefile);
    	perror("Error was ");
    }
    
    int processGet(int fd, char buffer[])
    {
    
    	/* First we declare our local variable friends */
    	int file_fd, j, buflen, len; /* eventually we will open a file to serve. Then we will rule the earth */
    	long ret, i;		/* this is a multipurpose tool for
     determining if read() calls work */
    	char *fstr;			/* later, when we're checking for legal
     extensions, you'll thank me. Oh yea you will. */	
    
    	/* I'm not quite sure what this does...I think it keeps us from reading extra crap we don't need */
    	for (i=4; i<BUFSIZE;i++)
    	{
    		if (buffer[i] == ' ') 
    		{
    			buffer[i] = 0;
    			break;
    		}
    	}
    
    	/* Nice try, kid */
    	for (j=0;j<i-1;j++) if (buffer[j] ==  '.' && buffer[j+1] == '.') printf("no h4x0ring allowed");
    
    	/* let's default to index.html */
    	if (!strncmp(&buffer[0],"GET /\0",6) || !strncmp(&buffer[0],"get /\0",6) ) (void)strcpy(buffer,"GET /index.html");
    
    	/* verify that we serve this extension ... this reminds me, we
     need a way of at least capturing the query string */
    	buflen = strlen(buffer);
    	fstr = (char *)0;
    	for (i=0;extensions[i].ext != 0;i++)
    	{
    		len = strlen(extensions[i].ext);
    		if (!strncmp(&buffer[buflen-len], extensions[i].ext, len)) 
    		{
    			fstr = extensions[i].filetype;
    			break;
    		}
    	}
    	
    	if (fstr == 0) printf("not valid, .............."); /* we should be nicer */
    	
    	/* see if we can't open that file */
    	if ((file_fd = open(&buffer[5],O_RDONLY)) == -1) printf("file couldn't open");
    	
    	(void)sprintf(buffer,"HTTP/1.0 200 OK\r\nContent-Type: %s\r\n\r\n",fstr);
    	(void)write(fd,buffer,strlen(buffer));
    
    	while ((ret=read(file_fd,buffer,BUFSIZE))>0) 
    	{
    		(void) write(fd,buffer,ret);
    	}
    
    #ifdef LINUX
    	sleep(1);
    #endif
    	close(fd);
    	
    	return 1;
    }
    
    void *processRequest(void *arg)
    {
    	static char buffer[BUFSIZE+1]; /* this is where we will buffer
     the HTTP request */
    
    	/* because arg is void in order to work with the thread library I'm using */
    	int fd = (int*)arg; 
    
    	long ret;
    
    	/* Now we read the HTTP request and see what it is */
    	ret = read(fd,buffer,BUFSIZE);
    	if (ret == 0 || ret == -1) printf("error, man. freakin' error."); /* <-- eventually we hope to implement a logging system,
     where all instances of printf 
    
    										will go bye bye */
    	
    	if (ret > 0 && ret < BUFSIZE) buffer[ret] = 0; /* makes it easier to tell when you're at the end */
    	else buffer[0] = 0;
    
    	char *firsthalf = strtok(buffer,"\r\n");
    	char *lasthalf = strtok(NULL,"\r\n");
    
    	writeFile("log.txt",lasthalf);
    
    	if (!strncmp(buffer,"GET",3) || ! strncmp(buffer,"get",3))
    	{
    		if (processGet(fd,buffer)) printf("yes");
    		else printf("no");
    	}
    
    	return NULL;
    }
    
    int main (int argc, char ** argv)
    {
    	/* Local variable time! */
    	int fd, port, connection_fd, socklen; /* stream file handle, port, connection handle, and the ... wtf is socklen? */
    	struct sockaddr_in sockaddr; /* this is our gateway to the
     internet, man */
    	pthread_t pth; 
    	
    	/*let's become a daemon real quick */
    	int i;
    	for (i =0;i<32;i++) (void)close(i);
    	(void)setpgrp();
    
    	fd = socket(PF_INET, SOCK_STREAM,0);
    	sockaddr.sin_family = AF_INET;
    	sockaddr.sin_addr.s_addr = INADDR_ANY;
    	port = atoi(argv[1]);            /* this two part cliffhanger takes
     the char argument, turns it into an int, and then puts it in
     network byte order*/
    	sockaddr.sin_port = htons(port);
    
    	bind (fd, (struct sockaddr*)&sockaddr, sizeof(sockaddr)); /* bind the socket */
    		
    	writeFile("donner.pid",(char *)getpid());
    
    	listen(fd, 1); /* begin listening .......... head */
    	
    	while (1) /* go forever, should probably find a way to change
     that */
    	{
    		connection_fd; /* this is the file descriptor for our
     session stream */
    		socklen = sizeof(sockaddr); /* it's just necessary, okay? */
    		
    		connection_fd = accept(fd, (struct sockaddr *)&sockaddr, &socklen); /* make the connection meaningful */
    		
    		pthread_create(&pth,NULL,processRequest,connection_fd); /* create our thread to process the request */
    		pthread_join(pth,NULL); /* then kill it when it's done */
    	}
    
    	pthread_join(pth,NULL); /* it doesn't hurt */
    
    	return 0;
    }
    For compilation, this works:
    Code:
     gcc donner.c -o donner -lpthread
    And to run it:

    ./donner portnumber &

    Please tell me what I am doing wrong. I have put the file i/o stuff in a neat function at the top, writeFile(), to make things easier, but all the code is provided anyway for contextual purposes.

    Anyone who can tell me what the issue is gets a quarter in the coin of spirit . Thank you!
    Last edited by rokenrol; 07-05-2006 at 03:00 PM. Reason: some language issues, (English) syntactical error

  2. #2
    Gawking at stupidity
    Join Date
    Jul 2004
    Location
    Oregon, USA
    Posts
    3,218
    Well, just a quick glance this is the first problem I found:
    Code:
    void writeFile(char *filename, char *toWrite)
    {
    	FILE * thefile;
    	thefile = fopen(filename,"wb");
    	fwrite(toWrite,sizeof toWrite, 1, thefile);
    	(void)fclose(thefile);
    	perror("Error was ");
    }
    sizeof toWrite is only returning the size of a pointer. You might want to use strlen() instead.
    If you understand what you're doing, you're not learning anything.

  3. #3
    Registered User
    Join Date
    Feb 2006
    Posts
    16

    Part 2: Things get weirder

    So, my server has been repaired and works pretty well, save for one little problem: originally, if a browser requested http://whatever.com/folder with no trailing slash at the end, the page would load fine HTML wise, but all pictures referenced would be searched for in the top level directory. I decided, okay, easy enough, I'll detect when there isn't a trailing slash and then redirect them to the same URL but with the slash, in essence silently forcing them to http://whatever.com/folder/. This is my attempted code (note: prefix is the absolute URL of the request on the server computer, and reqfile is the relative one sent by the browser):

    Code:
    if (isDir(prefix)) 
    	{ 
    		len = strlen(prefix);
    		if (prefix[len-1] != 47) /* is last char "/" ? */
    		{
    			fprintf(stdout, "Trailing slash for '%s'. Request: %s\n", reqfile, requrl);
    			
    			buffer = "";
    			char *header = 
    
    			(void)sprintf(buffer,"HTTP/1.1 302 Found\r\nLocation: %s/\r\n", reqfile); /* TROUBLE AREA */
    			
    			fprintf(stdout, "%s", buffer);
    			
    			if ( write(fd,buffer,strlen(buffer)) < 0 ) fprintf(stdout, "Error sending header!\n");
    			
    			#ifdef LINUX
    				sleep(1);
    			#endif			
    			
    			close(fd);
    			return 1; 
    		}
    		
    		requrl = strcat(prefix,"index.html");		
    	}
    While building this, I tested that isDir() does work, that the if logic does correctly observe trailing slashes or not, and that all but the line marked /* TROUBLE AREA */ work.

    Well, now, the problem is quite a pickle because, with some debug output, I find this HTTP response is being sent back:

    Code:
    HTTP/1.1 302 Found\r\nLocation: /1.1 302 Found
    Hmm...the HTTP response spits part of itself out where a valid directory should go. In the browser, I am confronted with the notice that I am being sent the malformed new URL of content-type null (point being, the debug output is accurate).

    The debug output beforehand proves that reqfile DOES contain the correct information; nowhere in the program does that variable hold an HTTP response. The problem is literally with that one line, and I have no idea why.

    Thank you for your consideration.
    Last edited by rokenrol; 07-08-2006 at 11:59 PM. Reason: Incorrect information regarding my problem

  4. #4
    Registered User
    Join Date
    Feb 2006
    Posts
    16
    Oh, and thank you to itsme; I figured out my other problem, but your observation was very helpful as well.

  5. #5
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    > buffer = "";
    If buffer was previously a char* pointing at allocated memory, then now it's pointing at an empty const string (which can't be modified).
    If you were just trying to empty the string before sprintf, that would be pointless.

    Of course, without seeing how you declared and initialised buffer, there could be several other problems as well.

    > if (prefix[len-1] != 47)
    Use if (prefix[len-1] != '/' )
    for readability

    > char *header =
    As posted, this doesn't even compile.
    Garbage In, Garbage Out.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  6. #6
    Registered User
    Join Date
    Feb 2006
    Posts
    16
    Salem: your remarks were duly noted. Unfortunately, the buffer="" and header="" nonsense made no difference when taken out.

    Here is the total and complete program; here is some clarification for variable names:

    prefix is named so because it starts as just a lowly (eventually user defined) path to the document root, and is concatenated onto forever. Reqfile is prefix minus the docroot. My philosophy is get it working first, then pretty up the code.

    My server takes a few priceless code snippets from a program called nweb. Also, I realize a lot is hardcoded; again, get it working, then abstract things like that.

    Without further ado,

    Code:
    /* Donner ... "to give" in French. It's a server...get it? Eh? :(
    
    Only one file thus far (this one): donner.c
    
    */
    
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #include <syslog.h>
    #include <dirent.h>
    
    /* Arg format: 1=port */
    
    #define BUFSIZE 8196
    
    struct {
    	char *ext;
    	char *filetype;
    } extensions [] = {
    	{"gif","image/gif"},
    	{"jpg","image/jpeg"},
    	{"jpeg","image/jpeg"},
    	{"png","image/png"},
    	{"zip","image/zip"},
    	{"gz","image/gz"},
    	{"tar","image/tar"},
    	{"htm","text/html"},
    	{"html","text/html"},
    	{"c","text/text"},
    	{0,0}
    };
    
    void writeFile(char *filename, char *toWrite)
    {
    	FILE * thefile;
    	thefile = fopen(filename,"w");
    	fprintf(thefile,"%s",toWrite);
    	fclose(thefile);
    	perror("Error was ");
    }
    
    int isDir(char *candidate)
    {
    	DIR * dir = opendir(candidate);
    	
    	if (dir == NULL)
    	{
    		return 0;
    	} else
    	{
    		return 1;
    	}
    }
    
    int processGet(int fd, char * buffer)
    {
    	int j,ret,len,buflen,file_fd;
    	long i;	
    	char *fstr;
    
    	/* for (j=0;j<i-1;j++) if (buffer[j] ==  '.' && buffer[j+1] == '.') printf("no h4x0ring allowed"); */
    
    	char *firstline;
    	char *reqtype; /* Slightly useless since only GET requests make it this far */
    	char *reqfile;
    	char *requrl;
    
    	firstline = strtok(buffer,"\r\n");
    	reqtype = strtok(firstline," ");
    	reqfile = strtok(NULL," ");
    
    	char prefix[BUFSIZE+1] = "/home/abbas/donner";
    	char error[BUFSIZE+1] = "./error404.html";
    	requrl = strcat(prefix,reqfile);
    
    	if (isDir(prefix)) 
    	{ 
    		len = strlen(prefix);
    		if (prefix[len-1] != 47) /* is last char "/" ? */
    		{
    			fprintf(stdout, "Trailing slash for '%s'. Request: %s\n", reqfile, requrl);
    			
    			requrl = strcat(reqfile,"/");
    
    			(void)sprintf(buffer,"HTTP/1.1 302 Found\r\nLocation: %s/\r\n", reqfile);
    			
    			fprintf(stdout, "%s", buffer);
    			
    			if ( write(fd,buffer,strlen(buffer)) < 0 ) fprintf(stdout, "Error sending header!\n");
    			
    			#ifdef LINUX
    				sleep(1);
    			#endif			
    			
    			close(fd);
    			return 1; 
    		}
    		
    		requrl = strcat(prefix,"index.html");		
    	}
    
    		if ((file_fd = open(&prefix,O_RDONLY)) == -1) file_fd = open("./error404.html",O_RDONLY);
    
    		buflen = strlen(prefix);
    		fstr = (char *)0;
    		for (i=0;extensions[i].ext != 0;i++)
    		{
    			len = strlen(extensions[i].ext);
    			if (!strncmp(&prefix[buflen-len], extensions[i].ext, len)) 
    			{
    				fstr = extensions[i].filetype;
    				break;
    			}
    		}
    	
    		/* see if we can't open that file */
    		
    		(void)sprintf(buffer,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\n\r\n",fstr);
    		(void)write(fd,buffer,strlen(buffer));
    	
    		while ((ret=read(file_fd,buffer,BUFSIZE))>0) 
    		{
    			(void) write(fd,buffer,ret);
    		}
    	
    	#ifdef LINUX
    		sleep(1);
    	#endif
    	
    	close(fd);
    	
    	return 1;
    
    }
    
    void *processRequest(void *arg)
    {
    	static char buffer[BUFSIZE+1]; /* this is where we will buffer the HTTP request */
    
    	/* because arg is void in order to work with the thread library I'm using */
    	int fd = (int*)arg; 
    
    	long ret;
    
    	/* Now we read the HTTP request and see what it is */
    	ret = read(fd,buffer,BUFSIZE);
    	
    	if (ret == 0 || ret == -1) printf("error, man. freakin' error."); /* <-- eventually we hope to implement a logging
     system, where all instances of printf 
    										will go bye bye */
    	
    	if (ret > 0 && ret < BUFSIZE) buffer[ret] = 0; /* makes it
     easier to tell when you're at the end */
    	else buffer[0] = 0;
    
    	if (!strncmp(buffer,"GET",3) || ! strncmp(buffer,"get",3))
    	{
    		processGet(fd,buffer);
    	}
    
    	return NULL;
    }
    
    int main (int argc, char ** argv)
    {
    		/* Local variable time! */
    		int fd, port, connection_fd, socklen; /* stream file
    
     handle, port, connection handle, and the ... wtf is socklen? */
    
    		struct sockaddr_in sockaddr; /* this is our gateway to the internet, man */
    		pthread_t pth; 
    
    		fd = socket(PF_INET, SOCK_STREAM,0);
    		sockaddr.sin_family = AF_INET;
    		sockaddr.sin_addr.s_addr = INADDR_ANY;
    		port = atoi(argv[1]);            /* this two part cliffhanger
    
     takes the char argument, turns it into an int, and then puts it in
    
     network byte order*/
    		sockaddr.sin_port = htons(port);
    		
    		if ( bind (fd, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) != 0 ) printf("Couldn't open socket"); /* bind
     the socket */
    		
    		if ( listen(fd, 1) != 0 ) printf("Error opening socket!"); /*
     begin listening .......... head */
    		
    		while (1) /* go forever, should probably find a way to change that */
    		{
    			connection_fd; /* this is the file descriptor for our session stream */
    			socklen = sizeof(sockaddr); /* it's just necessary, okay? */
    			
    			connection_fd = accept(fd, (struct sockaddr *)&sockaddr, &socklen); /* make the connection meaningful */
    			
    			pthread_create(&pth,NULL,processRequest,connection_fd); /* create our thread to process the request */
    			pthread_join(pth,NULL); /* then kill it when it's done */
    		}
    	
    		pthread_join(pth,NULL); /* it doesn't hurt */		
    	
    		exit(EXIT_SUCCESS);
    		
    		printf("test");
    	
    }
    This relies on the pthread library...I think gcc comes with it. (This program can be compiled on any *nix OS, including MacOSX.) In case it slips your mind, compile with -lpthread to make pthread work.

    There you have it. The problem, and I know this for a fact, lies in the if block if (isDir(prefix)) { if (prefix[len-1] != 47) { /* etc */

    The problem only arises when requesting a folder with no trailing slash. Otherwise, fully functional static page server. Many thanks to any solutions.

    Thank you!

  7. #7
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    > reqfile = strtok(NULL," ");
    When you do this, you return a pointer to somewhere in the buffer which is being tokenised. You're not making a complete copy of the string.

    Which means when you do this
    requrl = strcat(reqfile,"/");
    You're going to trash some other part of the original string you tokenised.

    > sprintf(buffer,"HTTP/1.1 302 Found\r\nLocation: %s/\r\n", reqfile);
    Likewise, sprintf() is not defined for overlapping writes - reqfile is just a pointer to somewhere in buffer, and you're busy overwriting buffer with new data. If sprintf() gets past where reqfile is pointing, then all sorts of fun can happen.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  8. #8
    Registered User
    Join Date
    Feb 2006
    Posts
    16
    Many thanks, Salem! What doesn't kill me makes me stronger. And I'm here typing, so...woot.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Extract Title from plain text file
    By Todd88 in forum C++ Programming
    Replies: 10
    Last Post: 11-21-2008, 09:47 AM
  2. fstream file I/O, getting keystrokes
    By mcborn in forum C++ Programming
    Replies: 1
    Last Post: 03-24-2008, 01:04 AM
  3. Help with file reading/dynamic memory allocation
    By Quasar in forum C++ Programming
    Replies: 4
    Last Post: 05-17-2004, 03:36 PM
  4. file locking online
    By caroundw5h in forum Tech Board
    Replies: 6
    Last Post: 04-28-2004, 09:00 AM
  5. Binary search on a HUGE file.
    By Appoloinus in forum C++ Programming
    Replies: 12
    Last Post: 03-03-2003, 12:29 AM