/* file:                client.c
 * author:              Robert S. Laramee
 * class:               cs720/820
 * date started:        23 Apr 98
 * date "finished":     04 May 98
 * project:             assignment 5
 * description:         The client program is interactive and continually 
 *                      prompts the user for the operation to be performed 
 * and the parameters for this operation. The client then packs this 
 * information in the standard message format and sends it to the server. 
 * When a response is received from the server, the status of the request and
 * the response are displayed on the output screen. Multiple client processes 
 * can be running simultaneously. 
 *
 * The main program is a menu-driven loop that interactively gets input data 
 * from the user and calls the appropriate routines with their parameters. The
 * client program must create its message queue. Before exiting, the message 
 * queue must be deleted. The client sends requests to the server queue 
 * (standard predefined name). The server writes its response to the client 
 * queue (the name of client's queue is sent to the server in the request 
 * message). 
 *
 * usage: [operation] [file name] [mode or offset] [# of bytes]\n"
 *  
 * For a create operation (1) provide a 3 digit octal number as the mode. e.g.
 * operation and parameters : 1 file.txt 755 \n"
 *
 * For a read operation (2) provide the offset and number of bytes. e.g.
 * operation and parameters: 2 file.txt 10 1000 \n"
 *
 * To quit (3) type 3 or hit '^c' e.g. \n"
 * operation and parameters : 3 \n"
 *
 * Compile the program using the -lrt option on Christa or Alberti
 * e.g. % gcc -lrt -o client client.c
 */

#include       /* for printf(), scanf(), fopen(), perror() etc. */
#include      /* for EXIT_FAILURE                              */
#include      /* for strlen(), strncopy()                      */
#include       /* for strerror(), errno                         */
#include   /* for open(), lseek()                           */
#include    /* for open()                                    */
#include       /* for open()                                    */
#include      /* for read(), write(), lseek()                  */
#include      /* for sigemptyset(), sigfillset() etc.          */
#include      /* for mq_open(), mq_send(), mq_recieve etc.     */
#include "message.h"     /* the header file with some data strctures      */

/* signal handler for ctrl c (^c) */
void control_c() {

    char *clientQ_name = "client_queue";

    sprintf(clientQ_name, "%d_queue", getpid());

    /* delete the client queue when exiting */
    unlink(clientQ_name);
    printf("\n exiting... \n");
    exit(0);
}

/* This function converts the octal mode typed in by the user into a mode
 * that the create system call can use
 * Note the following rules for the octal number:
 * 
 *     400  Permits read by owner.
 *     200  Permits write by owner.
 *     100  Permits execute or search by owner.
 *     040  Permits read by group.
 *     020  Permits write by group.
 *     010  Permits execute or search by group.
 *     004  Permits read by others.
 *     002  Permits write by others.
 *     001  Permits execute or search by others.
 */
mode_t convert_mode(int octal_number) {

    mode_t create_mode=0;

    if ((octal_number < 0) || (octal_number > 777)) {

        fprintf(stderr, "invalid octal number for create mode \n");
	return -1;
    }

    if (octal_number/400 == 1) {

	create_mode = create_mode | S_IRUSR;        /* assignment bitwise OR */
	octal_number = octal_number % 400;
    }
    if (octal_number/200 == 1) {

	create_mode = create_mode | S_IWUSR;        /* assignment bitwise OR */
	octal_number = octal_number % 200;
    }
    if (octal_number/100 == 1) {

	create_mode = create_mode | S_IXUSR;        /* assignment bitwise OR */
	octal_number = octal_number % 100;
    }

    if (octal_number/40 == 1) {

	create_mode = create_mode | S_IRGRP;
	octal_number = octal_number % 40;
    }
    if (octal_number/20 == 1) {

	create_mode = create_mode | S_IWGRP;
	octal_number = octal_number % 20;
    }
    if (octal_number/10 == 1) {

	create_mode = create_mode | S_IXGRP;
	octal_number = octal_number % 10;
    }

    if (octal_number/4 == 1) {

	create_mode = create_mode | S_IROTH;
	octal_number = octal_number % 4;
    }
    if (octal_number/2 == 1) {

	create_mode = create_mode | S_IWOTH;       
	octal_number = octal_number % 2;
    }
    if (octal_number/1 == 1) {

	create_mode = create_mode | S_IXOTH;
	octal_number = octal_number % 1;
    }

    return create_mode;
}

/* Generate a create request message for the client. Send the request across 
 * to the server and process the response from the client. 
 */
int client_create(REQUEST *request, mqd_t server_descriptor 
		                  , mqd_t client_descriptor, int priority) {

    char clientQ_buffer[MAX_MSG];           /* client Q message buffer       */
    RESPONSE *response;                     /* pointer to server's response  */

   /* send the server a request message */
    if(mq_send(server_descriptor,(char *)request, MAX_MSG, priority) != 0){

	perror("error sending message to server : (errno) \n");
	return -1;
    }

   /* receive server's response  message */
    if(mq_receive(client_descriptor, clientQ_buffer, MAX_MSG, &priority) 
	                                                                == 0) {
	perror("error receiving message from server: (errno) \n");
	return -1;
    } else {

	response = (RESPONSE *)clientQ_buffer;
	    if (response->status == UNSUCCESS) {

		printf("%s", response->data);

	    } else {

		printf("%s", response->data);
	    }
    } /* end if (mq_receive...) */
    return 0;
}
/* Generate the client's read request message. If the read request is larger 
 * than MAX_MSG size, it should be broken into several requests of size not 
 * exceeding MAX_MSG. Each request consists of the file-name, offset, and 
 * number of bytes to be read. The client waits for the response from the 
 * server before sending the next request. 
 */
int client_read(REQUEST *request, mqd_t server_descriptor
		                , mqd_t client_descriptor, int priority) {

    int i=0;
    int bytes_remaining=0;                  /* bytes remaining in read       */
    char clientQ_buffer[MAX_MSG];           /* client Q message buffer       */
    RESPONSE *response;                     /* pointer to server's response  */

    bytes_remaining = request->num_bytes;

    while (bytes_remaining > MAX_MSG) {

        request->num_bytes = MAX_MSG -5 ; /* leave room for end of line char */

       /* send the server a request message */
        if(mq_send(server_descriptor,(char *)request, MAX_MSG, priority) != 0){

	    perror("error sending message to server : (errno) \n");
            return -1;
	}

       /* receive server's response  message */
	if(mq_receive(client_descriptor, clientQ_buffer, MAX_MSG, &priority) 
	                                                                == 0) {
	    perror("error receiving message from server: (errno) \n");
	    return -1;
	} else {

	    response = (RESPONSE *)clientQ_buffer;
	    
	    if (response->status == UNSUCCESS) {

		printf("error reading data \n");

	    } else {

		for(i=0; i < MAX_MSG - 4; i++) {

		    printf("%c", response->data[i]);
		}
	    }
	} /* end if (mq_receive...) */

        /* update the file offset and bytes remaining */
        request->mode_or_offset = request->mode_or_offset + (MAX_MSG - 5);
	bytes_remaining = bytes_remaining - (MAX_MSG - 5);

    } /* end while() */

    request->num_bytes = bytes_remaining;

   /* send the server a request message */
    if(mq_send(server_descriptor,(char *)request, MAX_MSG, priority) != 0){

	perror("error sending message to server : (errno) \n");
        return -1;
    }

   /* receive server's response  message */
    if(mq_receive(client_descriptor, clientQ_buffer, MAX_MSG, &priority) 
	                                                                == 0) {
	perror("error receiving message from server: (errno) \n");
	return -1;
    } else {

	response = (RESPONSE *)clientQ_buffer;

	if (response->status == UNSUCCESS) {
	    
	    printf("error reading data \n");

	} else {

	    for(i=0; i < bytes_remaining; i++) {

		printf("%c", response->data[i]);
	    }
	    printf("\n");
	}
    } /* end if (mq_receive... */
    return 0;
}

int main (void) {

    int  operation=0;              /* operation MSG_CREATE = 1, MSG_READ = 2 */
    int  offset=0;                 /* IF read THEN offset                    */
    int  num_bytes=0;              /* IF read THEN number of bytes to read   */
    int  priority=0;               /* priority of message                    */
    int  octal_number=0;           /* create mode, (octal number)            */

    char input_buffer[4];          /* to put octal number in                 */
    char file_name[MAX_NAME];      /* name of file to read or create         */
    char *serverQ_name = "server_queue";    /* server queue name             */
    char *clientQ_name = "client_queue";    /* client queue name             */

    struct mq_attr attribute;               /* message queue attributes      */
    struct sigaction controlc_action;       /* signal action structure       */
    REQUEST *request;                       /* pointer to REQUEST structure  */

    mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO;
    mode_t create_mode=0;                   /* create mode used by creat()   */
    mqd_t server_descriptor;                /* server Q descripitor          */
    mqd_t client_descriptor;                /* client Q descripitor          */
    attribute.mq_maxmsg = 200;
    attribute.mq_msgsize = MAX_MSG;

   /* install SIGINT (control c) signal handler */
    controlc_action.sa_handler = control_c;
    controlc_action.sa_flags = 0;
    sigemptyset(&controlc_action.sa_mask);
    if (sigaction(SIGINT, &controlc_action, NULL) == -1)
        perror("\n Could not set up SIGINT handler for control_c() action \n");

   /* give the client message queue a unique name i.e. it's process ID
    * concatenated with the "_queue" extension.
    */
     sprintf(clientQ_name, "%d_queue", getpid());

    /* create client message queue */
 client_descriptor = mq_open(clientQ_name, O_RDONLY|O_CREAT, mode, &attribute);

     if (client_descriptor == -1) {

	 perror("Error opening client message queue: (errno) \n");
         exit(EXIT_FAILURE);
     }

   /* open server's message queue */
    server_descriptor = mq_open(serverQ_name, O_RDWR, mode, &attribute);

    if (server_descriptor == -1) {

        perror("error opening server message queue: (errno) \n");
        exit(EXIT_FAILURE);
    }

   /* get a  REQUEST structure */
    if ((request = (REQUEST *)malloc(sizeof(REQUEST))) == NULL) {
	perror("error allocating memory for request : (errno) \n");
	exit(EXIT_FAILURE);
    }

 printf("/** \n"
	" *  usage: [operation] [file name] [mode or offset] [# of bytes]\n"
	" *  \n"
	" *  For a create operation (1) provide a 3 digit octal number as "
	"the mode. e.g. \n"
	" *  operation and parameters : 1 file.txt 755 \n"
	" * \n"
	" *  For a read operation (2) provide the offset and number of bytes."
	" e.g. \n"
	" *  operation and parameters: 2 file.txt 10 1000 \n"
	" * \n"
	" *  To quit (3) type 3 or hit '^c' e.g. \n"
	" *  operation and parameters : 3 \n"
	" */ \n"
     );

    while(1) {

        printf("operation and parameters : ");
        scanf("%d", &operation);

	if (operation == QUIT) {

            sprintf(clientQ_name, "%d_queue", getpid());
           /* delete the client queue when exiting */
	    unlink(clientQ_name);
	    printf(" exiting \n");
	    exit(0);
	}

	scanf("%s", file_name);

        /* fill the request structure */
        strncpy(request->client_msgQ_name, clientQ_name, MAX_NAME);
        request->request_type = operation;
        strncpy(request->file_name, file_name, MAX_NAME);

        if (operation == MSG_CREATE) {

            gets(input_buffer);
	    octal_number = atoi(input_buffer);

	    create_mode = convert_mode(octal_number);

            if (create_mode == -1) {

		fprintf(stderr, "error converting mode for create \n");
	    }

	    request->mode_or_offset = (int)create_mode;
           
        } else if (operation == MSG_READ) {

	    scanf("%d %d", &offset, &num_bytes);
	    request->mode_or_offset = offset;
	    request->num_bytes = num_bytes;
        }

        if (request->request_type == MSG_CREATE) {

	    if ((client_create(request, server_descriptor
			              , client_descriptor, priority)) == -1) {

		fprintf(stderr, "error with client_create() \n");
            }
        } else if (request->request_type == MSG_READ) {

	    if ((client_read(request, server_descriptor
			            , client_descriptor, priority)) == -1) {

		fprintf(stderr, "error with client_create() \n");
            }
	} else {

	    fprintf(stderr, "unrecognized mode \n");
	}

    } /* end while(1) */
    free(request);           /* avoid memory leaks */
    return 0;
}