├── .gitignore ├── Makefile ├── README ├── TODO ├── client_udp.c ├── client_udp.h ├── commons.h ├── commons_functions.c ├── run.sh ├── server_udp.c ├── server_udp.h └── server_udp_functions.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.out 3 | .*.swo 4 | .*.swp 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: server_udp.c.out client_udp.c.out 2 | 3 | server_udp.c.out: server_udp.o server_udp_functions.o commons_functions.o 4 | gcc -lpthread server_udp_functions.o commons_functions.o server_udp.o -o server_udp.c.out 5 | 6 | client_udp.c.out: client_udp.o commons_functions.o 7 | gcc client_udp.o commons_functions.o -o client_udp.c.out 8 | 9 | server_udp.o: server_udp.c 10 | gcc -c server_udp.c -o server_udp.o 11 | 12 | server_udp_functions.o: server_udp_functions.c 13 | gcc -c server_udp_functions.c -o server_udp_functions.o 14 | 15 | client_udp.o: client_udp.c 16 | gcc -c client_udp.c -o client_udp.o 17 | 18 | commons_functions.o: commons_functions.c 19 | gcc -c commons_functions.c -o commons_functions.o 20 | 21 | clean: 22 | rm -f server_udp.o 23 | rm -f server_udp_functions.o 24 | rm -f client_udp.o 25 | rm -f commons_functions.o 26 | rm -f server_udp.c.out 27 | rm -f client_udp.c.out 28 | 29 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Simple RTP Client+Server 2 | ======================== 3 | 4 | Description 5 | ----------- 6 | RTP stands for Remote Timestamp Protocol. The implementation of RTP 7 | consists of a client application and a server application; the client 8 | can ask for the (last modified) timestamp of a file on the remote 9 | machine while the server satisfies that request. 10 | RTP uses User Datagram Protocol (UDP) as its transport layer protocol. 11 | 12 | High-Level Implementational Details 13 | ----------------------------------- 14 | The server creates a socket in the internet domain bound to port 15 | SERVER_PORT. The server receives requests through this port, 16 | processes the requests and replies to the client which had made the 17 | request. The client also creates a socket in the internet domain, 18 | sends requests to SERVER_PORT of a given IP address (i.e., server's 19 | IP address), and receives replies and displays results obtained from 20 | the server. The state-machines for a single connection for client and 21 | server are as the following: 22 | Client side protocol state machine: 23 | | 24 | | begin 25 | | 26 | ____V_____ 27 | | | 28 | | closed |________ 29 | |________| \ startup, request 30 | /\ \ 31 | | \ 32 | | | 33 | | _____V_____ 34 | done/ | | | 35 | done_acknowledgement | request | 36 | | | sent | 37 | | |_________| 38 | | | 39 | | | 40 | | | 41 | ____|____ | 42 | | | / 43 | | wait, | / request_acknowledgement 44 | | done |/________/ 45 | |_______|\ 46 | 47 | Server side protocol state machine: 48 | | 49 | | begin 50 | | 51 | ____V_____ 52 | | | 53 | | closed |________ 54 | |________| \ request, request_acknowledgement 55 | /\ \ 56 | | \ 57 | | | 58 | | _____V_____ 59 | | | | 60 | done_acknowledgement | new | 61 | | | request | 62 | | |_________| 63 | | | 64 | | | 65 | | | 66 | ____|____ | 67 | | | / 68 | | done | / query file timestamp, done 69 | | sent |/________/ 70 | |_______|\ 71 | 72 | The server handles each client with a separate thread; the main thread 73 | of the server spawns a new thread at a new incoming request. Since UDP 74 | is connectionless, the incoming datagrams are matched to their threads 75 | using connection_id. The threads the server can spawn are bounded by an 76 | upper limit (i.e., the threads reside in a finite-sized thread pool). 77 | Threads are not made to wait on spin-waiting loops, conditional-wait 78 | with mutex has been used to make threads wait and to notify a waiting 79 | thread. A thread after serving a client goes back to the pool of 80 | available threads. If the pool of available threads becomes empty at 81 | some stage and a new incoming request is pending, that request is 82 | dropped (which is justified since the underlying transport protocol is 83 | UDP anyway). 84 | The information between the client and the server is exchanged by means 85 | of custom packets. A packet has the following fields: 86 | 1. connection_id 87 | : given by the server if the request is a valid one and 88 | there is at least one thread in the pool of 89 | available threads. 90 | 2. type 91 | : varies among 92 | REQU (request) 93 | RACK (request_acknowledgement) 94 | DONE (done) 95 | DACK (done_acknowledgement) 96 | 3. status 97 | : determines the success or failure of finding the 98 | timestamp of a file. 99 | 4. buffer 100 | : contains data (garbage value if the packet is 101 | a control packet only). 102 | Since the byte order of the client, server and the network can all 103 | vary, the packets are converted to native architecture supported 104 | byte order when received and to network byte order at the time of 105 | sending. 106 | 107 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | make Makefile generic (as in FTP). 2 | sockaddr_in for client can be removed from client_info. 3 | 4 | -------------------------------------------------------------------------------- /client_udp.c: -------------------------------------------------------------------------------- 1 | #include "client_udp.h" 2 | 3 | int main(int argc, char* argv[]) 4 | { 5 | if(argc != 2) 6 | er("incorrect usage", -1); 7 | 8 | // BEGIN: initialization 9 | struct sockaddr_in sin_server; 10 | int socket_fd, x; // socket_fd: socket file descriptor for the client to connect to. x: stores error code of system calls. 11 | size_t size_sockaddr = sizeof(struct sockaddr), size_packet = sizeof(struct packet); 12 | short int connection_id; // temporary storage for the connection id of the client-server communication. 13 | struct packet* chp = (struct packet*) malloc(size_packet); // chp: client host packet 14 | struct packet* data; // data: network packet 15 | 16 | if((x = socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) // intializes the socket file descriptor for client-server communication (client side). 17 | er("socket()", x); 18 | 19 | memset((char*) &sin_server, 0, sizeof(struct sockaddr_in)); // clearing out the struct before initalization. 20 | sin_server.sin_family = AF_INET; 21 | sin_server.sin_port = htons(PORTSERVER); 22 | 23 | if(!(x = inet_aton(IPSERVER, &sin_server.sin_addr))) 24 | er("inet_aton()", x); 25 | 26 | printf(ID "UDP Client started up. Attempting communication with server @ %s:%d...\n\n", IPSERVER, PORTSERVER); 27 | // END: initialization 28 | 29 | // BEGIN: request 30 | set0(chp); // clearing out the struct before initalization. 31 | chp->type = REQU; 32 | chp->conid = -1; 33 | strcpy(chp->buffer, argv[1]); 34 | 35 | printpacket(chp, HP); 36 | 37 | printf(ID "Requesting Server for timestamp of: %s ...\n", chp->buffer); 38 | fflush(stdout); 39 | 40 | data = htonp(chp); 41 | if((x = sendto(socket_fd, data, size_packet, 0, (struct sockaddr*) &sin_server, size_sockaddr)) == -1) 42 | er("request sendto()", x); 43 | // END: request 44 | 45 | // BEGIN: request acknowledgement 46 | set0(data); // clearing out the struct before initalization. 47 | if((x = recvfrom(socket_fd, data, size_packet, 0, (struct sockaddr*) &sin_server, &size_sockaddr)) == -1) 48 | er("request acknowledgement recvfrom()", x); 49 | 50 | chp = ntohp(data); 51 | 52 | printpacket(chp, HP); 53 | 54 | connection_id = chp->conid; 55 | // do error checking here... 56 | // ... 57 | 58 | printf(ID "Reply from Server: %s\n", chp->buffer); 59 | fflush(stdout); 60 | // END: request acknowledgement 61 | 62 | // BEGIN: done 63 | set0(data); // clearing out the struct before initalization. 64 | if((x = recvfrom(socket_fd, data, size_packet, 0, (struct sockaddr*) &sin_server, &size_sockaddr)) == -1) 65 | er("done recvfrom()", x); 66 | 67 | chp = ntohp(data); 68 | 69 | printpacket(chp, HP); 70 | 71 | // do error checking here... 72 | // ... 73 | 74 | printf(ID "Reply from Server: %s", chp->buffer); 75 | fflush(stdout); 76 | // END: done 77 | 78 | // BEGIN: done acknowledgement 79 | set0(chp); // clearing out the struct before initalization. 80 | chp->type = DACK; 81 | chp->conid = connection_id; 82 | strcpy(chp->buffer, "Thanks!"); 83 | 84 | printpacket(chp, HP); 85 | 86 | data = htonp(chp); 87 | if((x = sendto(socket_fd, data, size_packet, 0, (struct sockaddr*) &sin_server, size_sockaddr)) == -1) 88 | er("done acknowledgement sendto()", x); 89 | 90 | printf(ID "Bye!\n"); 91 | fflush(stdout); 92 | // END: done acknowledgement 93 | 94 | // BEGIN: cleanup 95 | free(chp); 96 | free(data); 97 | close(socket_fd); 98 | printf(ID "Done.\n"); 99 | fflush(stdout); 100 | // END: cleanup 101 | 102 | return 0; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /client_udp.h: -------------------------------------------------------------------------------- 1 | #include "commons.h" 2 | 3 | #define IPSERVER "127.0.0.1" 4 | #define ID "CLIENT=> " // identifies the messages sent to stdout by the client. 5 | 6 | -------------------------------------------------------------------------------- /commons.h: -------------------------------------------------------------------------------- 1 | #include 2 | /* 3 | for: 4 | htons() 5 | htonl() 6 | ntohs() 7 | inet_aton() 8 | inet_ntoa() 9 | */ 10 | 11 | #include 12 | /* 13 | for: 14 | inet_aton() 15 | inet_ntoa() 16 | */ 17 | 18 | #include 19 | /* 20 | for: 21 | socket() 22 | bind() 23 | recvfrom() 24 | sendto() 25 | stat() 26 | */ 27 | 28 | #include 29 | /* 30 | for: 31 | socket() 32 | bind() 33 | recvfrom() 34 | sendto() 35 | inet_aton() 36 | inet_ntoa() 37 | AF_INET 38 | SOCK_DGRAM 39 | */ 40 | 41 | #include 42 | /* 43 | for: 44 | return type of system calls 45 | */ 46 | 47 | #include 48 | /* 49 | for: 50 | printf() 51 | sprintf() 52 | fflush() 53 | perror() 54 | NULL 55 | */ 56 | 57 | #include 58 | /* 59 | for: 60 | exit() 61 | malloc() 62 | */ 63 | 64 | #include 65 | /* 66 | for: 67 | memset() 68 | strlen() 69 | strcpy() 70 | */ 71 | 72 | #include 73 | /* 74 | for: 75 | close() 76 | sleep() 77 | stat() 78 | */ 79 | 80 | #define DEBUG 1 // used to toggle "debug mode". 81 | 82 | #define LENBUFFER 506 // so as to make the whole packet well-rounded ( = 512 bytes) 83 | #define PORTSERVER 8484 84 | 85 | #define REQU 1 86 | #define RACK 2 87 | #define DONE 3 88 | #define DACK 4 89 | 90 | #define NP 0 // network packet 91 | #define HP 1 // host packet 92 | 93 | 94 | // print errors and error codes to stderr 95 | #define er(e, x) \ 96 | do \ 97 | { \ 98 | perror("ERROR IN: " #e "\n"); \ 99 | fprintf(stderr, "%d\n", x); \ 100 | exit(-1); \ 101 | } \ 102 | while(0) 103 | 104 | // the following packet structure serves for both host and network packet, the subsequent conversion functions handle the nature of the packet. 105 | struct packet 106 | { 107 | short int conid; 108 | short int type; 109 | short int status; 110 | char buffer[LENBUFFER]; 111 | }; 112 | 113 | void set0(struct packet*); // sets memory allocated to a packet to 0. 114 | 115 | struct packet* ntohp(struct packet*); // converts a packet from network to host. 116 | struct packet* htonp(struct packet*); // converts a packet from host to network. 117 | 118 | void printpacket(struct packet*, int); // prints the contents and the nature of a packet. prints only in the debug mode. 119 | 120 | -------------------------------------------------------------------------------- /commons_functions.c: -------------------------------------------------------------------------------- 1 | #include "commons.h" 2 | 3 | void set0(struct packet* p) // sets memory allocated to a packet to 0. 4 | { 5 | memset(p, 0, sizeof(struct packet)); 6 | } 7 | 8 | struct packet* ntohp(struct packet* np) // converts a packet from network to host. 9 | { 10 | size_t size_packet = sizeof(struct packet); 11 | struct packet* hp = (struct packet*) malloc(size_packet); 12 | memset(hp, 0, size_packet); 13 | 14 | hp->conid = ntohs(np->conid); 15 | hp->type = ntohs(np->type); 16 | hp->status = ntohs(np->status); 17 | strcpy(hp->buffer, np->buffer); // char* datatype doesn't need conversion; it is copied as it is. 18 | 19 | return hp; 20 | } 21 | 22 | struct packet* htonp(struct packet* hp) // converts a packet from host to network. 23 | { 24 | size_t size_packet = sizeof(struct packet); 25 | struct packet* np = (struct packet*) malloc(size_packet); 26 | memset(np, 0, size_packet); 27 | 28 | np->conid = ntohs(hp->conid); 29 | np->type = ntohs(hp->type); 30 | np->status = ntohs(hp->status); 31 | strcpy(np->buffer, hp->buffer); // char* datatype doesn't need conversion; it is copied as it is. 32 | 33 | return np; 34 | } 35 | 36 | void printpacket(struct packet* p, int ptype) // prints the contents and the nature of a packet. prints only in the debug mode. 37 | { 38 | if(!DEBUG) 39 | return; 40 | 41 | if(ptype) 42 | printf("\t\tHOST PACKET\n"); 43 | else 44 | printf("\t\tNETWORK PACKET\n"); 45 | 46 | printf("\t\tconid = %d\n", p->conid); 47 | printf("\t\ttype = %d\n", p->type); 48 | printf("\t\tstatus = %d\n", p->status); 49 | printf("\t\tbuffer = %s\n\n", p->buffer); 50 | 51 | fflush(stdout); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | nclients=$1 4 | 5 | echo "Starting up Server..." 6 | ( ./server_udp.c.out & ) 7 | 8 | sleep 2s 9 | 10 | echo "Starting up $nclients Clients..." 11 | for (( i = 1; i <= $nclients; i++ )) 12 | do 13 | echo "Firing up Client #$i..." 14 | ( ./client_udp.c.out /home/rovin/Dropbox/playlist.m3u & ) 15 | done 16 | 17 | echo "Wait for processes to complete..." 18 | sleep 20s && killall server_udp.c.out && killall client_udp.c.out 19 | echo "Done." 20 | -------------------------------------------------------------------------------- /server_udp.c: -------------------------------------------------------------------------------- 1 | #include "server_udp.h" 2 | 3 | int socket_fd, x; // socket_fd: socket file descriptor for the server to connect to. x: stores error code of system calls. 4 | pthread_t threads[MAXTHREADS]; // thread-pool. 5 | pthread_mutex_t threads_mutex[MAXTHREADS]; // thread mutex array. 6 | pthread_cond_t threads_cond[MAXTHREADS]; // cond wait array. 7 | int threads_state[MAXTHREADS]; // strores the states of the threads according to id (= index of the array). 8 | 9 | void* serve_client(void* info); // serves the client's request till the client sends a "done acknowledgement". 10 | 11 | int main(void) 12 | { 13 | // BEGIN: initialization 14 | int nthreads = 0; // stores count of simultaneous clients being served at any time. 15 | struct sockaddr_in sin_server, sin_client; 16 | size_t size_sockaddr = sizeof(struct sockaddr), size_packet = sizeof(struct packet); 17 | struct packet* shp; // shp: server host packet 18 | struct packet* data = (struct packet*) malloc(size_packet); // data: network packet 19 | 20 | if((x = socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) // intializes the socket file descriptor for client-server communication (server side). 21 | er("socket()", x); 22 | 23 | memset((char*) &sin_server, 0, sizeof(struct sockaddr_in)); 24 | sin_server.sin_family = AF_INET; 25 | sin_server.sin_port = htons(PORTSERVER); 26 | sin_server.sin_addr.s_addr = htonl(INADDR_ANY); 27 | 28 | if((x = bind(socket_fd, (struct sockaddr*) &sin_server, size_sockaddr)) == -1) 29 | er("bind()", x); 30 | 31 | int i; 32 | for(i = 0; i < MAXTHREADS; i++) 33 | { 34 | threads_state[i] = CLOSED; // because initially, all threads in the pool are to be made available. 35 | 36 | if(x = pthread_mutex_init(&threads_mutex[i], NULL)) 37 | er("pthread_mutex_init()", x); 38 | 39 | if(x = pthread_cond_init(&threads_cond[i], NULL)) 40 | er("pthread_cond_init()", x); 41 | } 42 | 43 | printf(ID "UDP Server started up @ local:%d. Waiting for client(s)...\n\n", PORTSERVER); 44 | // END: initialization 45 | 46 | // the following loop should not be confused with "Busy Waiting"! It is merely meant to run indefinite times (till there is an error which exit()s the server or the user kills the server process). 47 | while(1) 48 | { 49 | // BEGIN: request + done acknowledgement 50 | set0(data); // clearing out the struct before initalization. 51 | if((x = recvfrom(socket_fd, data, size_packet, 0, (struct sockaddr*) &sin_client, &size_sockaddr)) == -1) 52 | er("request + done acknowledgement recvfrom()", x); 53 | 54 | shp = ntohp(data); 55 | 56 | printpacket(shp, HP); 57 | 58 | printf(ID "Packet received from %s:%d with data: %s\n", inet_ntoa(sin_client.sin_addr), ntohs(sin_client.sin_port), shp->buffer); 59 | fflush(stdout); 60 | if(shp->conid == -1) 61 | { 62 | // create a new thread if nthreads <= MAXTHREADS. 63 | if(!(nthreads <= MAXTHREADS)) 64 | { 65 | // drop the packet. 66 | 67 | if(DEBUG) 68 | fprintf(stderr, ID "PACKET DROPPED.\n"); 69 | 70 | continue; 71 | } 72 | 73 | nthreads++; 74 | 75 | if(DEBUG) 76 | fprintf(stderr, ID "CREATING A NEW THREAD.\n"); 77 | 78 | // assign id to the new connection. gives the id of the free thread having lowest index value. 79 | int j = 0; 80 | while(j < MAXTHREADS) 81 | { 82 | if(threads_state[j] == CLOSED) 83 | break; 84 | j++; 85 | } 86 | shp->conid = j; 87 | 88 | struct client_info* ci = client_info_alloc(shp, &sin_client); 89 | if(x = pthread_create(threads + nthreads, NULL, &serve_client, ci)) 90 | er("pthread_create()", x); 91 | } 92 | else if(shp->type == DACK) 93 | { 94 | pthread_cond_signal(&threads_cond[shp->conid]); 95 | nthreads--; 96 | 97 | printf(ID "Client says: %s\n", shp->buffer); 98 | fflush(stdout); 99 | } 100 | else 101 | { 102 | // do error checking here... 103 | // ... 104 | 105 | fprintf(stderr, "STRAY PACKET RECEIVED\n"); 106 | } 107 | // END: request + done acknowledgement 108 | } 109 | 110 | return 0; 111 | } 112 | 113 | void* serve_client(void* info) 114 | { 115 | // create local copies of the client information obtained via the (only) argument passed through pthread_create(). 116 | struct client_info* ci = (struct client_info*) info; 117 | struct packet cihp = ci->hp; 118 | struct packet* data; 119 | struct sockaddr_in cisinc = ci->sinc; 120 | char path[LENBUFFER], ts[LENBUFFER]; // path: temporary storage for the path of the file whose timestamp is being requested by the client. ts: temporary storage for the timestamp of the file whose timestamp is being requested by the client. 121 | size_t size_sockaddr = sizeof(struct sockaddr), size_packet = sizeof(struct packet); 122 | 123 | threads_state[cihp.conid] = OPEN; // renders the thread running current call of this function locked. 124 | 125 | strcpy(path, cihp.buffer); 126 | 127 | printpacket(&cihp, HP); 128 | 129 | // BEGIN: request acknowledgement 130 | cihp.type = RACK; 131 | sprintf(cihp.buffer, "Querying the timestamp of: %s", path); 132 | 133 | printpacket(&cihp, HP); 134 | 135 | data = htonp(&cihp); 136 | if((x = sendto(socket_fd, data, size_packet, 0, (struct sockaddr*) &cisinc, size_sockaddr)) == -1) 137 | er("request acknowledgement sendto()", x); 138 | 139 | printf(ID "Calling the timestamp function...\n"); 140 | fflush(stdout); 141 | // END: request acknowledgement 142 | 143 | // BEGIN: done 144 | cihp.type = DONE; 145 | strcpy(ts, timestamp(path)); 146 | if(!strcmp(ts, FNF)) 147 | cihp.status = -1; 148 | sprintf(cihp.buffer, "Last modified timestamp is: %s", ts); 149 | 150 | printpacket(&cihp, HP); 151 | 152 | data = htonp(&cihp); 153 | if((x = sendto(socket_fd, data, size_packet, 0, (struct sockaddr*) &cisinc, size_sockaddr)) == -1) 154 | er("done sendto()", x); 155 | 156 | printf(ID "Timestamp sent.\n"); 157 | fflush(stdout); 158 | // END: done 159 | 160 | pthread_cond_wait(&threads_cond[cihp.conid], &threads_mutex[cihp.conid]); // waits for the signal from the client connected to the thread running current call of this function to send "done acknowledgement". 161 | threads_state[cihp.conid] = CLOSED; // renders the thread running current call of this function unlocked (free for other clients to take up). 162 | 163 | pthread_exit(NULL); // terminates the thread running current call of this function. 164 | } 165 | 166 | -------------------------------------------------------------------------------- /server_udp.h: -------------------------------------------------------------------------------- 1 | #include "commons.h" 2 | 3 | #include 4 | /* 5 | for: 6 | ctime() 7 | */ 8 | 9 | #include 10 | /* 11 | for: 12 | stat() 13 | */ 14 | 15 | #include 16 | /* 17 | for: 18 | pthread_mutex_init() 19 | pthread_cond_init() 20 | pthread_create() 21 | pthread_cond_signal() 22 | pthread_cond_wait() 23 | pthread_exit() 24 | */ 25 | 26 | #define ID "SERVER=> " // identifies the messages sent to stdout by the server. 27 | #define MAXTHREADS 10 // a limit can be put to the maximum number of threads to be created at any time, hence limiting the number of clients served by the server simultaneously; the limit may be due to resource restriction. 28 | 29 | #define CLOSED 0 // state of a thread which is free to process a new client's request. (~Unlocked state) 30 | #define OPEN 1 // state of a thread which is busy processing a client's request. (~Locked state) 31 | 32 | #define FNF "THERE WAS AN ERROR IN LOCATING THE FILE.\n" // File Not Found error. 33 | 34 | // the following struct sets the information to be passed to a new thread created by the main thread of the server. 35 | struct client_info 36 | { 37 | struct packet hp; // packet (server host packet) as received from the client. 38 | struct sockaddr_in sinc; // the sockaddr_in struct of the client to be served by the (newly) created thread. 39 | }; 40 | 41 | struct client_info* client_info_alloc(struct packet*, struct sockaddr_in*); // allocates memory for the struct client_info. 42 | 43 | char* timestamp(char*); // locates the file and returns its last modified timestamp in a char*. 44 | 45 | -------------------------------------------------------------------------------- /server_udp_functions.c: -------------------------------------------------------------------------------- 1 | #include "server_udp.h" 2 | 3 | struct client_info* client_info_alloc(struct packet* p, struct sockaddr_in* s) // allocates memory for the struct client_info. 4 | { 5 | struct client_info* c = (struct client_info*) malloc(sizeof(struct client_info)); 6 | 7 | memcpy(&(c->hp), p, sizeof(struct packet)); 8 | memcpy(&(c->sinc), s, sizeof(struct sockaddr_in)); 9 | 10 | return c; 11 | } 12 | 13 | char* timestamp(char* path) // locates the file and returns its last modified timestamp in a char*. 14 | { 15 | int x; // stores error code of system calls. 16 | struct stat fst; 17 | memset((char*) &fst, 0, sizeof(struct stat)); 18 | char s[LENBUFFER] = FNF; 19 | 20 | if(x = stat(path, &fst)) // if the given path was not correct or the permissions of the path are not set appropriately 21 | return s; 22 | 23 | return ctime(&fst.st_mtime); 24 | } 25 | 26 | --------------------------------------------------------------------------------