Patchwork [3,of,3,RFC,-V3] Experimental, non-mature, convenience implementation of level 0 API

login
register
mail settings
Submitter Iulian Stana
Date July 25, 2013, 5:27 p.m.
Message ID <81b155e555d80acf3e5e.1374773267@doppler>
Download mbox | patch
Permalink /patch/1961/
State Deferred
Headers show

Comments

Iulian Stana - July 25, 2013, 5:27 p.m.
# HG changeset patch
# User Iulian Stana <julian.stana@gmail.com>
# Date 1374754623 -10800
#      Thu Jul 25 15:17:03 2013 +0300
# Node ID 81b155e555d80acf3e5eebd47cdff627ca7d6724
# Parent  a97753ae4036ba4ea34ccb67971d31c01f97ed4b
Experimental, non-mature, convenience implementation of level 0 API


I would like to apologize first, I know that I was explicitly asked not to write
this implementations, but it was very helpful for me at least to sketch out some
working code, in order to get familiar with the problem domain.

In this patch you will find the "client.c" file, that contains the
implementation for the level 0 API, "util.c" and "util.h" files that contain
useful functions, and the "Makefile" that can compile this code.

Patch

diff --git a/Makefile b/Makefile
new file mode 100644
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@ 
+CC = gcc
+CFLAGS = -Wall -g -std=c89
+TARGET = main
+
+build: main
+
+main: main.c utils.o client.o
+	$(CC) $(CFLAGS) utils.o client.o -o $(TARGET) main.c
+
+client.o: client.c 
+	$(CC) $(CFLAGS) -c client.c
+
+utils.o: utils.c
+	$(CC) $(CFLAGS) -c utils.c
+
+.PHONY: clean
+
+clean:
+	rm -f *.o *~ $(TARGET)
diff --git a/client.c b/client.c
new file mode 100644
--- /dev/null
+++ b/client.c
@@ -0,0 +1,285 @@ 
+#define _GNU_SOURCE 
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+
+#include "client.h"
+#include "utils.h"
+
+#define HGPATH "hg"
+
+/*
+ * The function will read the header from the command server and will save it to
+ * the header parameter of the handle structure.
+ * \param handle The handle of the connection, wherewith I want to communicate
+ * \return 0 if succesfull 
+ *        -1 to indicate an error, with errno set appropriately
+ * */
+int read_header(hg_handle *handle)
+{
+	uint32_t length;
+	char ch_char;
+
+	if (!handle) {
+		return -1;
+	}
+
+	read(handle->p_read, &ch_char, 1);
+	read(handle->p_read, &length, sizeof(uint32_t));
+	handle->header.channel = ch_char;
+	handle->header.length = swap_uint32(length);
+
+	return 0;
+}
+
+/*
+ * After the connection, the command server sends a hello message.
+ * The message contains the command server capabilities and the messages
+ * encoding.
+ * \param handle The handle of the connection, wherewith I want to communicate
+ * \return 0 if succesfull 
+ *        -1 to indicate an error, with errno set appropriately
+ * */
+int read_hello(hg_handle *handle)
+{
+	char *buffer;
+	hg_header ch;
+
+	if (!handle) {
+		return -1;
+	}
+
+	read_header(handle);
+	ch = handle->header;
+
+	if(ch.length == 0)
+		return -1;
+
+	buffer = malloc(ch.length + 1);
+
+	read(handle->p_read, buffer, ch.length);
+	buffer[ch.length] = '\0';
+
+	printf("%c%u", ch.channel, ch.length);
+	printf("%s\n", buffer);
+
+	free(buffer);
+
+	return 0;
+}
+
+/*
+ * Open the connection with the mercurial command server.
+ * */
+hg_handle *hg_open(const char *path, char *encoding)
+{
+	hg_handle *handle = malloc(sizeof(hg_handle));
+	char command[100];
+	int wpipe[2];
+	int rpipe[2];
+	int c_write;
+	int c_read;
+
+	sprintf(command,
+		"%s serve --cmdserver pipe --config ui.interactive=True",
+		HGPATH);
+
+	if (path)
+		sprintf(command, "%s -R %s", command, path);
+
+	if (pipe(wpipe) < 0 || pipe(rpipe) < 0) {
+		printf("The connection failed\n");
+	}
+	handle->p_read = rpipe[0];
+	c_write = rpipe[1];
+	c_read = wpipe[0];
+	handle->p_write = wpipe[1];
+
+	if ((handle->childpid = fork()) < 0) {
+		printf("Fork failed\n");
+		return NULL;
+
+	} else if (handle->childpid == 0) {	/* child */
+		close(handle->p_write);
+		close(handle->p_read);
+		dup2(c_read, STDIN_FILENO);
+		close(c_read);
+		dup2(c_write, STDOUT_FILENO);
+		close(c_write);
+		execl("/bin/sh", "sh", "-c", command, NULL);
+
+	} else {	/* parent */
+		close(c_read);
+		close(c_write);
+	}
+
+	if(read_hello(handle) == -1)
+		return NULL;
+
+	return handle;
+}
+
+/*
+ * Close the connection for the given handle.
+ * */
+int hg_close(hg_handle **handle)
+{
+	if (!(*handle)) {
+		return -1;
+	}
+
+	kill((*handle)->childpid, SIGKILL);
+
+	close((*handle)->p_read);
+	close((*handle)->p_write);
+
+	free(*handle);
+	*handle = NULL;
+
+	return 0;
+}
+
+/*
+ * Prepare the command for sending process. 
+ * Replace all the blank space with the '\0' character.
+ * \param command mercurial command with option parameters, separated through
+ *                  spaces
+ * \return Mercurial command with option parameters, separated through '\0'
+ * Ex:
+ *      "tip -p" -> "tip\0-p"
+ * */
+char *cmd_prepare(char *const command[], size_t *cmd_size)
+{
+	size_t cmd_length = 0;
+	char *new_cmd;
+	int i;
+
+	for(i = 0; i < *cmd_size; ++i){
+		if(!command[i]){
+			*cmd_size = i;
+			break;
+		}
+		cmd_length += strlen(command[i]) + 1;
+	}
+
+	new_cmd = malloc(cmd_length + 1);
+
+	for(i = 0; i < *cmd_size; ++i){
+		strcpy(new_cmd, command[i]);
+		new_cmd += strlen(command[i]) + 1; 
+	}
+	new_cmd -= cmd_length;
+
+	*cmd_size = cmd_length - 1;
+	return new_cmd;
+}
+
+/*
+ * Sending a command to the mercurial command server, through the given handle.
+ * */
+int hg_rawcommand(hg_handle *handle, char *const command[], size_t cmd_size)
+{
+	if (!handle) {
+		return -1;
+	}
+
+	char runcommand[] = "runcommand\n";
+	char *cmd_send = cmd_prepare(command, &cmd_size);
+	uint32_t big_endian_size = swap_uint32(cmd_size);
+
+	write(handle->p_write, runcommand, strlen(runcommand));
+	write(handle->p_write, &big_endian_size, sizeof(uint32_t));
+	write(handle->p_write, cmd_send, cmd_size);
+
+	read_header(handle);
+
+	free(cmd_send);
+	return 0;
+}
+
+/*
+ * Reading some unparse data from the server.
+ * */
+int hg_rawread(hg_handle *handle, char *buffer, size_t sizebuff)
+{
+	int length = handle->header.length;
+	if (!handle) {
+		return -1;
+	}
+	if(!(hg_channel(handle) == 'o' || hg_channel(handle) == 'e'))
+		return 0;
+
+	length = (length > sizebuff)? sizebuff : length;
+
+	read(handle->p_read, buffer, length);
+	buffer[length] = '\0';
+	handle->header.length -= length;
+
+	if(!handle->header.length)
+		read_header(handle);
+
+	return length;
+}
+
+/*
+ * Will write the buffer to the server.
+ * */
+int hg_rawwrite(hg_handle *handle, const char *buffer, size_t buff_size)
+{
+	int length = handle->header.length;
+	uint32_t swap_size;
+	if (!handle) {
+		return -1;
+	}
+	length = (length > buff_size)? buff_size : length;
+	swap_size = swap_uint32(length);
+
+	write(handle->p_write, &swap_size, sizeof(uint32_t));
+	write(handle->p_write, buffer, length);
+
+	read_header(handle);
+	return length;
+}
+
+/*
+ * The channel for the next chunk of data.
+ * */
+char hg_channel(hg_handle *handle)
+{
+	if (!handle) {
+		return -1;
+	}
+
+	return handle->header.channel;
+}
+
+/*
+ * The header for the next chunk of data.
+ * */
+hg_header hg_head(hg_handle *handle)
+{
+	return handle->header;
+}
+
+/*
+ * The exitcode for the current command.
+ * */
+int hg_exitcode(hg_handle *handle)
+{
+	int exitcode;
+
+	if (!handle) {
+		return -1;
+	}
+
+	read(handle->p_read, &exitcode, sizeof(int));
+
+	return exitcode;
+}
diff --git a/utils.c b/utils.c
new file mode 100644
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,14 @@ 
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "utils.h"
+
+/* 
+ * Byte swap unsigned int
+ * */
+uint32_t swap_uint32(uint32_t val)
+{
+	val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF);
+	return (val << 16) | (val >> 16);
+}
diff --git a/utils.h b/utils.h
new file mode 100644
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,12 @@ 
+#ifndef _UTILS_CHG_H_
+#define _UTILS_CHG_H_
+
+#include <stdint.h>
+#include <stdlib.h>
+
+/* 
+ * Byte swap unsigned int
+ * */
+uint32_t swap_uint32( uint32_t val );
+
+#endif