]> oss.titaniummirror.com Git - repo_shell.git/commitdiff
Code for supporting git ACLs. acl_test is useful.
authorR. Steve McKown <rsmckown@gmail.com>
Wed, 26 Sep 2012 21:26:24 +0000 (15:26 -0600)
committerR. Steve McKown <rsmckown@gmail.com>
Wed, 26 Sep 2012 22:21:40 +0000 (16:21 -0600)
13 files changed:
.gitignore
Makefile
acl_test.c [new file with mode: 0644]
git_acl.c [new file with mode: 0644]
git_acl.cfg.example [new file with mode: 0644]
git_acl.h [new file with mode: 0644]
mystrtok.c [new file with mode: 0644]
mystrtok.h [new file with mode: 0644]
mystrtok_test.c [new file with mode: 0644]
stra.c [new file with mode: 0644]
stra.h [new file with mode: 0644]
utility.c [new file with mode: 0644]
utility.h [new file with mode: 0644]

index f4255f98bebedef006fb6d5eb1430a20e9166903..c7859a14ead6811b9133fc422a79d21ec3986e53 100644 (file)
@@ -1,4 +1,6 @@
 repo_shell
+acl_test
+mystrtok_test
 version.c
 *.swp
 .svn
index dc3b5f843748e741739279838a459b6c8717e6f5..f6259972cb5a2b6d7ccc3bad5d5abfdc9c91ad94 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-PROGRAM := repo_shell
+MAIN := repo_shell
 # Git derives its version "prefix" from tags
 
 # Add TMI's mkversion to render files xxx.in -> xxx, updating __appVersion__
@@ -11,7 +11,17 @@ INFILES := $(shell ls *.in 2>/dev/null)
 INFILES := $(INFILES:%.in=%)
 #$(warning INFILES $(INFILES))
 
-repo_shell: repo_shell.c inih/ini.c version.c
+PROGRAMS = $(MAIN) mystrtok_test acl_test
+
+all: $(MAIN)
+
+repo_shell: repo_shell.c inih/ini.c mystrtok.c stra.c utility.c version.c
+       $(CC) -I inih $^ -o $@
+
+mystrtok_test: mystrtok_test.c mystrtok.c utility.c
+       $(CC) -g $^ -o $@
+
+acl_test: acl_test.c git_acl.c inih/ini.c mystrtok.c stra.c utility.c
        $(CC) -I inih $^ -o $@
 
 $(INFILES):%: %.in
@@ -19,6 +29,6 @@ $(INFILES):%: %.in
        @diff -q $@-tmp $@ >/dev/null 2>&1 && rm -f $@-tmp || mv $@-tmp $@
 
 clean:
-       @rm -rf $(PROGRAM) $(INFILES)
+       @rm -rf $(PROGRAMS) $(INFILES)
 
 .PHONY: all clean $(INFILES)
diff --git a/acl_test.c b/acl_test.c
new file mode 100644 (file)
index 0000000..f8d5f71
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2012, Titanium Mirror, Inc.
+ * All Rights Reserved.
+ *
+ * This document is the proprietary and confidential property of
+ * Titanium Mirror, Inc.  All use, distribution, reproduction or re-distribution
+ * is disallowed without the prior express written consent of
+ * Titanium Mirror, Inc.
+ */
+
+/*
+ * Test parsing of the git acl config file
+ *
+ * @author R. Steve McKown <rsmckown@gmail.com>
+ */
+
+#include <stdio.h>
+#include "git_acl.h"
+
+int main(int argc, char **argv)
+{
+  char *file = "git_acl.cfg.example";
+  perms_t p;
+
+  if (argc != 3)
+    die("usage: %s <username> <reponame>\n", argv[0]);
+
+  p = git_acl(argv[1], argv[2], file); /* file=NULL for default */
+
+  printf("config file %s\n"
+      "GITACL(%s@%s) = %s(%u)\n  via repoid='%s', userid='%s'\n",
+      file, argv[1], argv[2], git_acl_perms_as_str(p), p,
+      git_acl_last_repoid(), git_acl_last_userid());
+  return 0;
+}
diff --git a/git_acl.c b/git_acl.c
new file mode 100644 (file)
index 0000000..e077f78
--- /dev/null
+++ b/git_acl.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright © 2012, Titanium Mirror, Inc.
+ * All Rights Reserved.
+ *
+ * This document is the proprietary and confidential property of
+ * Titanium Mirror, Inc.  All use, distribution, reproduction or re-distribution
+ * is disallowed without the prior express written consent of
+ * Titanium Mirror, Inc.
+ */
+
+/*
+ * Parse the git_acl.cfg file to find the first permissions therein matching the
+ * requested user and repo names.
+ *
+ * @author R. Steve McKown <rsmckown@gmail.com>
+ */
+
+#include <stdbool.h>
+//#include <stdio.h>
+//#include <errno.h>
+//#include <stdlib.h>
+//#include <sys/types.h>
+//#include <fcntl.h>
+//#include <unistd.h>
+//#include <pwd.h>
+#include <string.h>
+#include "ini.h"
+#include "utility.h"
+#include "mystrtok.h"
+#include "stra.h"
+#include "git_acl.h"
+
+enum {
+  DFLT_IDS_SZ = 32
+};
+
+typedef struct {
+  char *repo;
+  char *user;
+  stra_t repoids;
+  stra_t userids;
+  perms_t perms;
+} acl_t;
+
+const char* perm_str[PERMS_COUNT] = {
+  "NOTFOUND", "NONE", "READ", "READ_WRITE"
+};
+
+const char* lm_none = "<none>";
+static char *lm_repoid = NULL;
+static char *lm_userid = NULL;
+static char lm_perms = PERMS_NOTFOUND;
+
+static char *get_lm_repoid()
+{
+  return (lm_repoid) ? lm_repoid : (char*)lm_none;
+}
+
+static void set_lm_repoid(const char* newstr)
+{
+  if (lm_repoid)
+    free(lm_repoid);
+  if (newstr)
+    lm_repoid = xstrdup(newstr);
+}
+
+static char *get_lm_userid()
+{
+  return (lm_userid) ? lm_userid : (char*)lm_none;
+}
+
+static void set_lm_userid(const char* newstr)
+{
+  if (lm_userid)
+    free(lm_userid);
+  if (newstr)
+    lm_userid = xstrdup(newstr);
+}
+
+static const char *perms_as_str(perms_t p)
+{
+  if (p < PERMS_NOTFOUND || p >= PERMS_COUNT)
+    die("perms_as_str: invalid perm %u", p);
+  return perm_str[p];
+}
+
+static perms_t perms_from_str(const char *str)
+{
+  perms_t p = PERMS_NOTFOUND;
+
+  if (!str)
+    return PERMS_NOTFOUND;
+  else if (!*str)
+    return PERMS_NONE;
+  else if (!strcmp(str, "r"))
+    return PERMS_READ;
+  else if (!strcmp(str, "rw"))
+    return PERMS_READ_WRITE;
+  else
+    die("Invalid perms value '%s'", str);
+}
+
+static acl_init(acl_t *acl)
+{
+  acl->repo = acl->user = NULL;
+  stra_init(&acl->repoids, DFLT_IDS_SZ);
+  stra_init(&acl->userids, DFLT_IDS_SZ);
+  acl->perms = PERMS_NOTFOUND;
+}
+
+static acl_clear(acl_t *acl)
+{
+  stra_destroy(&acl->repoids);
+  stra_destroy(&acl->userids);
+}
+
+static int str_has_word(const char* string, const char* word)
+{
+  char *_s = xstrdup(string);
+  char *s = _s;
+  char *p = my_strtok(&s, " \t\n");
+
+  while (p) {
+    if (!strcmp(p, word)) {
+      free(_s);
+      return true;
+    }
+    p = my_strtok(&s, " \t\n");
+  }
+  free(_s);
+  return false;
+}
+
+static int acl_handler(void* user, const char* section, const char* name,
+    const char* value)
+{
+  acl_t* acl = (acl_t*)user;
+
+  if (acl->perms)
+    return 1; /* Already matched an ACL entry for repo+user */
+
+  //debug("section='%s', name='%s', value='%s'", section, name, value);
+
+  if (!strcmp(section, "user_groups")) {
+    if (str_has_word(value, acl->user)) {
+      //debug("userids += '%s'", name);
+      stra_add(&acl->userids, name);
+    }
+  } else if (!strcmp(section, "repo_groups")) {
+    if (str_has_word(value, acl->repo)) {
+      //debug("repoids += '%s'", name);
+      stra_add(&acl->repoids, name);
+    }
+  } else if (!strncmp(section, "repo", 4)) {
+    char *_p = xstrdup(section + 4);
+    char *p = _p;
+    char *repo = my_strtok(&p, " \t\n");
+
+    if (!repo || my_strtok(&p, " \t\n"))
+      die("acl_handler: badly formatted section '%s'", section);
+    /* repo is repo name, name is userid, value is permission */
+    if (stra_find(&acl->repoids, repo) >= 0 &&
+        stra_find(&acl->userids, name) >= 0) {
+      acl->perms = perms_from_str(value);
+      set_lm_repoid(repo);
+      set_lm_userid(name);
+      lm_perms = acl->perms;
+      //debug("match: repoid='%s', userid='%s', perms='%s'(%u)", repo, name,
+      //    value, acl->perms);
+    }
+    free(_p);
+  } else
+    die("acl_handler: unknown section='%s' name='%s'", section, name);
+  return 1;
+}
+
+int git_acl(char *user, char *repo, char *file)
+{
+  acl_t acl;
+
+  if (!file || !*file)
+    file = GIT_ACL_FILE;
+  if (!user || !*user || !repo || !*repo) {
+    die("git_acl: invalid args user='%s', repo='%s', file='%s'", user, repo,
+       file);
+  }
+
+  set_lm_repoid(NULL);
+  set_lm_userid(NULL);
+  lm_perms = PERMS_NOTFOUND;
+  acl_init(&acl);
+  acl.user = user;
+  acl.repo = repo;
+  stra_add(&acl.userids, acl.user);
+  stra_add(&acl.repoids, acl.repo);
+
+  //debug("Searching for '%s'@'%s'", acl.user, acl.repo);
+  if (ini_parse(file, acl_handler, &acl) < 0)
+    die("cannot read acl file %s", file);
+  acl_clear(&acl); /* acl.perms remains unchanged */
+  return acl.perms;
+}
+
+char *git_acl_last_repoid() { return get_lm_repoid(); }
+
+char *git_acl_last_userid() { return get_lm_userid(); }
+
+int git_acl_last_perms() { return lm_perms; }
+
+const char *git_acl_perms_as_str(perms_t p) { return perms_as_str(p); }
diff --git a/git_acl.cfg.example b/git_acl.cfg.example
new file mode 100644 (file)
index 0000000..0b85beb
--- /dev/null
@@ -0,0 +1,62 @@
+# git_acl.cfg for use with repo_shell
+#
+# The [user_groups] section contains lines of the form:
+#   <ugroup> = user1 user2 ... userN
+#
+# The [repo_groups] section contains lines of the form:
+#   <rgroup> = repo1 repo2 ... repoN
+#
+# Nested user and repo groups are not supported.  Therefore, user group names
+# (ugroup or rgroup) used as an element defining another group will treat the
+# former as a user or a repo, respectively.
+#
+# The [repo <repoid>] section contains lines of the form:
+#   <userid> = <perms>
+# These lines define the permissions (<perms>) for one or more <userid>'s
+# (users or user groups) for the repository (or repository group) <repoid>.
+#
+# The first <userid> line in the first [repo <repoid>] section matching the user
+# and repo for which permissions are requested will satisfy the request.  Any
+# other possible matches later in the configuration file are irrelevant.
+#
+# A user/repo combination that has no match in the git_acl.cfg file is reported
+# as the separate NOTFOUND permission, but is effectively treated the same as
+# the NONE permission.
+
+[user_groups]
+devs = smckown dlotton jobu
+losers = frank bill ted mike
+
+[repo_groups]
+mirrors = mirrors/tinyos.git mirrors/chibios.git
+public = oss-web.git repo_shell.git cp210x.git
+private = redmine.git nesc.git
+
+[repo nesc.git]
+smckown =
+ted = rw
+losers = r
+
+[repo oss-web.git]
+smckown = rw
+devs = r
+
+[repo mirrors/tinyos.git]
+jobu = rw
+smckown = r
+devs = -
+losers = r
+
+[repo public]
+dlotton = rw
+devs = -
+losers = r
+
+[repo private]
+smckown = rw
+dlotton = rw
+devs = 
+
+[repo mirrors]
+jobu = r
+devs = rw
diff --git a/git_acl.h b/git_acl.h
new file mode 100644 (file)
index 0000000..80440cc
--- /dev/null
+++ b/git_acl.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright © 2012, Titanium Mirror, Inc.
+ * All Rights Reserved.
+ *
+ * This document is the proprietary and confidential property of
+ * Titanium Mirror, Inc.  All use, distribution, reproduction or re-distribution
+ * is disallowed without the prior express written consent of
+ * Titanium Mirror, Inc.
+ */
+
+/*
+ * Parse the git_acl.cfg file to find the first permissions therein matching the
+ * requested user and repo names.
+ *
+ * @author R. Steve McKown <rsmckown@gmail.com>
+ */
+
+#ifndef GIT_ACL_H
+#define GIT_ACL_H
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <string.h>
+#include "ini.h"
+#include "utility.h"
+#include "mystrtok.h"
+#include "stra.h"
+
+#define GIT_ACL_FILE "git_acl.cfg"
+
+typedef enum {
+  PERMS_NOTFOUND = 0,
+  PERMS_NONE,
+  PERMS_READ,
+  PERMS_READ_WRITE,
+  PERMS_COUNT
+} perms_t;
+
+int git_acl(char *user, char *repo, char *file);
+char *git_acl_last_repoid();
+char *git_acl_last_userid();
+int git_acl_last_perms();
+const char *git_acl_perms_as_str(perms_t p);
+
+#endif /* end of include guard: GIT_ACL_H */
diff --git a/mystrtok.c b/mystrtok.c
new file mode 100644 (file)
index 0000000..f075683
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2012, Titanium Mirror, Inc.
+ * All Rights Reserved.
+ *
+ * This document is the proprietary and confidential property of
+ * Titanium Mirror, Inc.  All use, distribution, reproduction or re-distribution
+ * is disallowed without the prior express written consent of
+ * Titanium Mirror, Inc.
+ */
+
+/*
+ * A custom strtok.  This version skips any number and any combination of
+ * delimiter characters between tokens.
+ *
+ * @author R. Steve McKown <rsmckown@gmail.com>
+ */
+
+#include <stddef.h>
+#include <strings.h>
+
+char *my_strtok(char **begin, char* delims)
+{
+  char *p;
+  char *q;
+
+  if (!begin)
+    return NULL;
+  if (!delims || !*delims || !*begin)
+    return (*begin = NULL);
+
+  q = *begin;
+  while (*q && index(delims, *q)) q++;
+  if (!*q)
+    return (*begin = NULL);
+  p = q;
+  while (*q && !index(delims, *q)) q++;
+  if (*q)
+    *q++ = 0;
+  *begin = q;
+  return p;
+}
diff --git a/mystrtok.h b/mystrtok.h
new file mode 100644 (file)
index 0000000..6968fa8
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright © 2012, Titanium Mirror, Inc.
+ * All Rights Reserved.
+ *
+ * This document is the proprietary and confidential property of
+ * Titanium Mirror, Inc.  All use, distribution, reproduction or re-distribution
+ * is disallowed without the prior express written consent of
+ * Titanium Mirror, Inc.
+ */
+
+/*
+ * A custom strtok.  This version skips any number and any combination of
+ * delimiter characters between tokens.
+ *
+ * @author R. Steve McKown <rsmckown@gmail.com>
+ */
+
+#ifndef MYSTRTOK_H
+#define MYSTRTOK_H
+
+char *my_strtok(char **begin, char* delims);
+
+#endif /* end of include guard: MYSTRTOK_H */
diff --git a/mystrtok_test.c b/mystrtok_test.c
new file mode 100644 (file)
index 0000000..5a8756e
--- /dev/null
@@ -0,0 +1,24 @@
+#include <stdio.h>
+#include "utility.h"
+#include "mystrtok.h"
+
+int main(int argc, char **argv)
+{
+  char *text;
+  char *p;
+
+  if (argc != 3) {
+    fprintf(stderr, "usage: %s \"A string to parse\" \"delimiters\"\n", argv[0]);
+    return 0;
+  }
+
+  text = argv[1];
+  putchar('|');
+  p = my_strtok(&text, argv[2]);
+  while (p) {
+    printf("%s|", p);
+    p = my_strtok(&text, argv[2]);
+  }
+  putchar('\n');
+  return 0;
+}
diff --git a/stra.c b/stra.c
new file mode 100644 (file)
index 0000000..4e2f04b
--- /dev/null
+++ b/stra.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright © 2012, Titanium Mirror, Inc.
+ * All Rights Reserved.
+ *
+ * This document is the proprietary and confidential property of
+ * Titanium Mirror, Inc.  All use, distribution, reproduction or re-distribution
+ * is disallowed without the prior express written consent of
+ * Titanium Mirror, Inc.
+ */
+
+/*
+ * A dynamic string array implementation.
+ *
+ * @author R. Steve McKown <rsmckown@gmail.com>
+ */
+
+#include <stdlib.h>
+#include "utility.h"
+#include "stra.h"
+
+void stra_init(stra_t *stra, size_t size)
+{
+  if (!stra)
+    die("stra_destroy: stra NULL reference");
+  stra->items = xmalloc(sizeof(char*) * size);
+  stra->count = 0;
+  stra->size = size;
+}
+
+void stra_destroy(stra_t *stra)
+{
+  unsigned i;
+
+  if (!stra)
+    die("stra_destroy: stra NULL reference");
+  for (i = 0; i < stra->count; i++)
+    free(stra->items[i]);
+  if (stra->size) {
+    free(stra->items);
+    stra->size = 0;
+  }
+}
+
+int stra_add(stra_t *stra, const char *item)
+{
+  if (!stra)
+    die("stra_add: stra NULL reference");
+  if (!item)
+    die("stra_in: item is NULL");
+  if (stra->count > stra->size)
+    die("str_add: bad count=%u, size=%u\n", stra->count, stra->size);
+  else if (stra->count == stra->size) {
+    /* Expand items[] */
+    if (stra->size)
+      stra->size *= 2;
+    else
+      stra->size = 1;
+    stra->items = xrealloc(stra->items, stra->size);
+  }
+  stra->items[stra->count] = xstrdup(item);
+  return stra->count++;
+}
+
+int stra_find(stra_t *stra, const char *item)
+{
+  unsigned i;
+
+  if (!stra)
+    die("stra_in: stra NULL reference");
+  if (!item)
+    die("stra_in: item is NULL");
+  for (i = 0; i < stra->count; i++) {
+    if (!strcmp(stra->items[i], item))
+      return i;
+  }
+  return -1;
+}
+
+size_t stra_count(stra_t *stra)
+{
+  if (!stra)
+    die("stra_count: stra NULL reference");
+  return stra->count;
+}
+
+size_t stra_size(stra_t *stra)
+{
+  if (!stra)
+    die("stra_size: stra NULL reference");
+  return stra->size;
+}
+
+char *stra_item(stra_t *stra, size_t ele)
+{
+  if (!stra)
+    die("stra_item: stra NULL reference");
+  if (ele > stra->count)
+    die("stra_item: ele out of range (%u > %u)", ele, stra->count);
+  return stra->items[ele];
+}
diff --git a/stra.h b/stra.h
new file mode 100644 (file)
index 0000000..a2f43d6
--- /dev/null
+++ b/stra.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright © 2012, Titanium Mirror, Inc.
+ * All Rights Reserved.
+ *
+ * This document is the proprietary and confidential property of
+ * Titanium Mirror, Inc.  All use, distribution, reproduction or re-distribution
+ * is disallowed without the prior express written consent of
+ * Titanium Mirror, Inc.
+ */
+
+/*
+ * A dynamic string array implementation.
+ *
+ * @author R. Steve McKown <rsmckown@gmail.com>
+ */
+
+
+#ifndef STRA_H
+#define STRA_H
+
+#include <stddef.h>
+
+typedef int (*stra_fneq_t)(void *item1, void *item2);
+
+typedef struct {
+  size_t count;
+  size_t size;
+  char **items;
+} stra_t;
+
+/* Initialize an stra structure, to hold up to size strings */
+void stra_init(stra_t *stra, size_t size);
+
+/* Destroy all dynamic memory associated with the stra.  Use init to reuse */
+void stra_destroy(stra_t *stra);
+
+/* Add an item to the end of the string array, returning its ele# */
+int stra_add(stra_t *stra, const char *item);
+
+/* Return the ele# of the first string matching item */
+int stra_find(stra_t *stra, const char *item);
+
+/* Return the count of items in the stra */
+size_t stra_count(stra_t *stra);
+
+/* Return the current size of the stra */
+size_t stra_size(stra_t *stra);
+
+/* Return a pointer to the item at ele# ele */
+char *stra_item(stra_t *stra, size_t ele);
+
+#endif /* end of include guard: STRA_H */
diff --git a/utility.c b/utility.c
new file mode 100644 (file)
index 0000000..ba7373b
--- /dev/null
+++ b/utility.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright © 2012, Titanium Mirror, Inc.
+ * All Rights Reserved.
+ *
+ * This document is the proprietary and confidential property of
+ * Titanium Mirror, Inc.  All use, distribution, reproduction or re-distribution
+ * is disallowed without the prior express written consent of
+ * Titanium Mirror, Inc.
+ */
+
+/*
+ * Common functions for command line utility programs
+ *
+ * @author R. Steve McKown <rsmckown@gmail.com>
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include "utility.h"
+
+void debug(const char *fmt, ...)
+{
+  va_list ap;
+
+  va_start(ap, fmt);
+  fprintf(stderr, "debug: ");
+  vfprintf(stderr, fmt, ap);
+  fprintf(stderr, "\n" );
+  va_end(ap);
+}
+
+void die(const char *fmt, ...)
+{
+  va_list ap;
+
+  va_start(ap, fmt);
+  fprintf(stderr, "error: ");
+  vfprintf(stderr, fmt, ap);
+  fprintf(stderr, "\n" );
+  va_end(ap);
+  exit(1);
+}
+
+void *xmalloc(size_t size)
+{
+  void *ret;
+
+  if (!size)
+    die("xmalloc: size cannot be zero");
+  ret = malloc(size);
+  if (!ret)
+    die("out of memory (malloc)");
+  return ret;
+}
+
+void *xrealloc(void *ptr, size_t size)
+{
+  void *ret;
+
+  if (!size)
+    die("xrealloc: size cannot be zero");
+  ret = realloc(ptr, size);
+  if (!ret)
+    die("out of memory (realloc)");
+  return ret;
+}
+
+char *xstrdup(const char *str)
+{
+  char *ret = strdup(str);
+  if (!ret)
+    die("out of memory (strdup)");
+  return ret;
+}
+
+char *xstrndup(const char *str, size_t n)
+{
+  char *ret = strndup(str, n);
+  if (!ret)
+    die("out of memory (strndup)");
+  return ret;
+}
diff --git a/utility.h b/utility.h
new file mode 100644 (file)
index 0000000..e17192c
--- /dev/null
+++ b/utility.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2012, Titanium Mirror, Inc.
+ * All Rights Reserved.
+ *
+ * This document is the proprietary and confidential property of
+ * Titanium Mirror, Inc.  All use, distribution, reproduction or re-distribution
+ * is disallowed without the prior express written consent of
+ * Titanium Mirror, Inc.
+ */
+
+/*
+ * Common functions for command line utility programs
+ *
+ * @author R. Steve McKown <rsmckown@gmail.com>
+ */
+
+#ifndef UTILITY_H
+#define UTILITY_H
+
+#include <stddef.h>
+#include <stdarg.h>
+
+void debug(const char *fmt, ...);
+void die(const char *fmt, ...);
+void *xmalloc(size_t size);
+void *xrealloc(void *ptr, size_t size);
+char *xstrdup(const char *str);
+char *xstrndup(const char *str, size_t n);
+
+#endif /* end of include guard: UTILITY_H */