+#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;
+}