--- /dev/null
+Reposerve protocol v1
+=====================
+
+Commands are variable-length strings ending with a newline character (10), with
+a maximum length of 256 characters, including the newline.
+
+Responses always start with a result line with either "OK", followed by a byte
+count of response data to follow, or an "ERR" followed by silence.
+
+All binary values in response data are in little-endian byte order (LSB-first).
+
+On connection establishment, the server sends a string of the form:
+ <server name>-<server version> protocol:<max protocol version supported>
+
+DOS filename mode
+-----------------
+Request: "dos"
+Description:
+When the client sends a dos request, the server enables DOS filename
+translation. Filename path components are automatically converted to DOS 8.3
+form when transmitted to the client. File lists transmitted by the client are
+translated back to the original names, capital letters for new untracked files
+are converted to lowercase, except for specific files such as "Makefile*",
+"GNUmakefile", "README*", and "COPYING*", otherwise for tracked files, the
+existing capitalization is maintained.
+
+File list
+---------
+Request: "flist"
+Response data:
+
+ offset | size | description
+ 0 | 4 | number of file entries (N)
+ 4 | 32 | file entry 0
+ 4+32 | 32 | file entry 1
+ ...
+ 4+i*32 | 32 | file entry i
+ ...
+ 4+N*32 | ?? | file name table
+
+
+ File entry (32 bytes):
+ offset | size | description
+ 0 | 16 | md5 checksum
+ 16 | 4 | last modification timestamp
+ 20 | 4 | file size
+ 24 | 4 | name string offset (from the start of the name table)
+ 28 | 4 | name string length
+
+Description:
+Provides a list of files managed by the repo server, with all the relevant
+metadata about each file.
+
+Push files
+----------
+Request: "push"
+Request data: Exactly the same as the "flist" response data, except that after
+each file name in the name table, the contents of the corresponding file are
+transmitted.
+
+Description:
+Although potentially every file can be sent using this request, the intent is to
+only push modified files according to either the modification timestamp or the
+checksum provided in the file list.
+
+Pull files
+----------
+Request: "pull"
+Request data: Exactly the same as the "flist" response data.
+Response data: Exactly the same as the "flist" response data, except that after
+the file name in the name table, the contents of the corresponding file are
+transmitted.
+
+Description:
+The server compares file checksums or modification timestamps in the request
+file list, and sends back only those files which are modified.
PREFIX ?= /usr/local
-src = $(wildcard src/*.c)
+src = $(wildcard src/*.c) $(wildcard ../src/*.c)
obj = $(src:.c=.o)
dep = $(src:.c=.d)
bin = reposerve
-CFLAGS = -pedantic -Wall -g -MMD
+CFLAGS = -pedantic -Wall -g -MMD -Isrc -I../src
LDFLAGS = -lgit2
$(bin): $(obj)
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "client.h"
+#include "proto.h"
+#include "repo.h"
+
+static int proc_request(struct client *c);
+static int proc_flist(struct client *c, char *data, int datasz);
+static int proc_push(struct client *c, char *data, int datasz);
+static int proc_pull(struct client *c, char *data, int datasz);
+
+const char *repo_path;
+
+int handle_client(struct client *c)
+{
+ char buf[512];
+ int sz, left;
+
+ while((sz = read(c->s, buf, sizeof buf)) > 0) {
+ left = sizeof c->buf - c->bsz;
+ if(sz > left) sz = left;
+ memcpy(c->buf + c->bsz, buf, sz);
+ c->bsz += sz;
+ }
+
+ if(proc_request(c) == -1) {
+ return -1;
+ }
+ return 0;
+}
+
+static int proc_request(struct client *c)
+{
+ char *endp = c->buf;
+ char *reqdata;
+ int reqdata_len;
+
+ while(endp < c->buf + c->bsz) {
+ if(*endp == '\n') break;
+ }
+ if(endp >= c->buf + c->bsz) {
+ return 0;
+ }
+ *endp = 0;
+
+ reqdata = endp + 1;
+ reqdata_len = c->bsz - (reqdata - c->buf);
+
+ c->bsz = 0;
+
+ if(strcmp(c->buf, "flist") == 0) {
+ return proc_flist(c, reqdata, reqdata_len);
+ } else if(strcmp(c->buf, "push") == 0) {
+ return proc_push(c, reqdata, reqdata_len);
+ } else if(strcmp(c->buf, "pull") == 0) {
+ return proc_pull(c, reqdata, reqdata_len);
+ } else if(strcmp(c->buf, "dos") == 0) {
+ c->flags |= CLIENT_DOS;
+ send_ok(c, 0);
+ return 0;
+ }
+
+ send_err(c);
+ return 0;
+}
+
+void send_string(struct client *c, const char *s)
+{
+ write(c->s, s, strlen(s));
+}
+
+void send_ok(struct client *c, int resp_sz)
+{
+ char buf[32];
+ sprintf(buf, "OK %d\n", resp_sz < 0 ? 0 : resp_sz);
+ send_string(c, buf);
+}
+
+void send_err(struct client *c)
+{
+ send_string(c, "ERR\n");
+}
+
+static struct flist *gen_flist(int contents)
+{
+ int i, count;
+ struct flist *flist;
+
+ if(!(flist = flist_create())) {
+ return 0;
+ }
+
+ if(repo_init(repo_path) == -1) {
+ return 0;
+ }
+ count = repo_num_files();
+
+ for(i=0; i<count; i++) {
+ flist_add(flist, repo_file(i)->path, contents);
+ }
+ repo_cleanup();
+
+ return flist;
+}
+
+static int proc_flist(struct client *c, char *data, int datasz)
+{
+ struct flist *flist;
+
+ if(!(flist = gen_flist(0))) {
+ send_err(c);
+ return -1;
+ }
+ flist_finalize(flist);
+
+ send_ok(c, flist->final_size);
+ write(c->s, flist->flist, flist->final_size);
+
+ flist_destroy(flist);
+ return 0;
+}
+
+static int proc_push(struct client *c, char *data, int datasz)
+{
+ send_err(c);
+ return 0;
+}
+
+static int proc_pull(struct client *c, char *data, int datasz)
+{
+ send_err(c);
+ return 0;
+}
--- /dev/null
+#ifndef CLIENT_H_
+#define CLIENT_H_
+
+enum {
+ CLIENT_DOS = 1
+};
+
+struct client {
+ int s;
+ unsigned int flags;
+ char buf[16384];
+ int bsz;
+ struct client *next;
+};
+
+int handle_client(struct client *c);
+
+void send_string(struct client *c, const char *s);
+void send_ok(struct client *c, int resp_sz);
+void send_err(struct client *c);
+
+#endif /* CLIENT_H_ */
static const char *guess_repo_name(const char *path);
static int parse_args(int argc, char **argv);
-static const char *repo_path, *repo_name;
+static const char *repo_name;
static int lis = -1;
static int port = 64357;
static struct client *clients;
+const char *repo_path;
+
+
int main(int argc, char **argv)
{
struct sockaddr_in addr;
perror("failed to accept connection");
continue;
}
- if(!(c = malloc(sizeof *c))) {
+ if(!(c = calloc(1, sizeof *c))) {
perror("failed to allocate memory for client structure");
close(s);
continue;
c->s = s;
c->next = clients;
clients = c;
+
+ send_string(c, "reposerve-0.1 protocol:0\n");
}
}
--- /dev/null
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include "proto.h"
+#include "md5.h"
+
+struct flist *flist_create(void)
+{
+ struct flist *flist;
+
+ if(!(flist = calloc(1, sizeof *flist))) {
+ perror("flist_create");
+ return 0;
+ }
+ flist->final_size = -1;
+ return flist;
+}
+
+void flist_destroy(struct flist *flist)
+{
+ if(flist) {
+ free(flist->flist);
+ free(flist->data);
+ free(flist);
+ }
+}
+
+int flist_add(struct flist *flist, const char *fname, int contents)
+{
+ FILE *fp;
+ struct proto_file_entry fent;
+ int namelen, datalen, sz;
+ struct stat st;
+ char *dptr;
+ char buf[1024];
+ struct md5_state md;
+
+ if(flist->final_size >= 0) {
+ fprintf(stderr, "flist_add: can't add more files to a finalized file list\n");
+ return -1;
+ }
+
+ if(!(fp = fopen(fname, "rb"))) {
+ fprintf(stderr, "flist_add: failed to open file: %s: %s\n", fname, strerror(errno));
+ return -1;
+ }
+ fstat(fileno(fp), &st);
+
+ fent.size = st.st_size;
+ fent.mtime = st.st_mtime;
+
+ namelen = datalen = strlen(fname);
+ if(contents) {
+ datalen += fent.size;
+ }
+
+ if(flist->flist->num_files >= flist->max_files) {
+ struct proto_flist *tmp;
+ int newsz = flist->max_files ? flist->max_files << 1 : 32;
+
+ if(!(tmp = realloc(flist->flist, sizeof *tmp + (newsz - 1) * sizeof *tmp->files))) {
+ fprintf(stderr, "flist_add: failed to resize file list to %d entries\n", newsz);
+ return -1;
+ }
+ flist->flist = tmp;
+ flist->max_files = newsz;
+ }
+ if(flist->data_sz + datalen >= flist->max_data_sz) {
+ char *tmp;
+ int newsz = flist->max_data_sz + datalen;
+ newsz |= newsz >> 1;
+ newsz |= newsz >> 2;
+ newsz |= newsz >> 4;
+ newsz |= newsz >> 8;
+ newsz |= newsz >> 16;
+ newsz += 1;
+
+ if(!(tmp = realloc(flist->data, newsz))) {
+ fprintf(stderr, "flist_add: failed to resize data buffer to %d bytes\n", newsz);
+ return -1;
+ }
+ flist->data = tmp;
+ flist->max_data_sz = newsz;
+ }
+
+ fent.nameoffs = flist->data_sz;
+ fent.namelen = namelen;
+ dptr = flist->data + flist->data_sz;
+ memcpy(dptr, fname, namelen);
+
+ md5_begin(&md);
+ if(contents) {
+ dptr += namelen;
+
+ while((sz = fread(dptr, 1, st.st_size, fp)) > 0) {
+ md5_msg(&md, dptr, sz);
+ dptr += sz;
+ st.st_size -= sz;
+ }
+ } else {
+ while((sz = fread(buf, 1, sizeof buf, fp)) > 0) {
+ md5_msg(&md, buf, sz);
+ }
+ }
+ md5_end(&md);
+ memcpy(fent.csum, md.sum, sizeof fent.csum);
+
+ fclose(fp);
+
+ flist->flist->files[flist->flist->num_files++] = fent;
+ flist->data_sz += datalen;
+ return 0;
+}
+
+int flist_finalize(struct flist *flist)
+{
+ int newsz;
+ struct proto_flist *tmp;
+
+ if(!flist->flist || flist->flist->num_files <= 0) {
+ fprintf(stderr, "flist_finalize: nothing to finalize, file list empty\n");
+ return -1;
+ }
+
+ newsz = sizeof *tmp + (flist->flist->num_files - 1) * sizeof *tmp->files + flist->data_sz;
+ if(!(tmp = realloc(flist->flist, newsz))) {
+ fprintf(stderr, "flist_finalize: failed to resize file list buffer to %d bytes\n", newsz);
+ return -1;
+ }
+
+ memcpy(tmp->files + flist->flist->num_files, flist->data, flist->data_sz);
+ free(flist->data);
+
+ memset(flist, 0, sizeof *flist);
+ flist->flist = tmp;
+ flist->final_size = newsz;
+ return 0;
+}
--- /dev/null
+#ifndef PROTO_H_
+#define PROTO_H_
+
+#include <stdint.h>
+
+struct proto_file_entry {
+ uint32_t csum[4];
+ uint32_t mtime;
+ uint32_t size;
+ uint32_t nameoffs;
+ uint32_t namelen;
+};
+
+struct proto_flist {
+ uint32_t num_files;
+ struct proto_file_entry files[1];
+};
+
+struct flist {
+ struct proto_flist *flist;
+ int max_files;
+ char *data;
+ int data_sz, max_data_sz;
+
+ int final_size;
+};
+
+struct flist *flist_create(void);
+void flist_destroy(struct flist *flist);
+
+int flist_add(struct flist *flist, const char *fname, int contents);
+int flist_finalize(struct flist *flist);
+
+#endif /* PROTO_H_ */