aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMartin Pärtel <martin.partel@gmail.com>2012-03-13 16:53:07 +0200
committerMartin Pärtel <martin.partel@gmail.com>2012-03-13 16:53:07 +0200
commit66d6de935a32fbf3aae2ef091b417a6572c8e6fd (patch)
tree10ad62b92e4d87d08b7b6d1c10e4c502a59c9271 /src
parentc6375194b97c7f879357a3c31a74f8376b42d344 (diff)
downloadbindfs-66d6de935a32fbf3aae2ef091b417a6572c8e6fd.tar.gz
Added --map.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am4
-rw-r--r--src/bindfs.139
-rw-r--r--src/bindfs.c269
-rw-r--r--src/usermap.c123
-rw-r--r--src/usermap.h60
5 files changed, 431 insertions, 64 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index f123526..1d5491c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,8 +2,8 @@
bin_PROGRAMS = bindfs
-noinst_HEADERS = debug.h permchain.h userinfo.h misc.h
-bindfs_SOURCES = bindfs.c permchain.c userinfo.c misc.c
+noinst_HEADERS = debug.h permchain.h userinfo.h misc.h usermap.h
+bindfs_SOURCES = bindfs.c permchain.c userinfo.c misc.c usermap.c
AM_CFLAGS = $(fuse_CFLAGS)
bindfs_LDADD = $(fuse_LIBS)
diff --git a/src/bindfs.1 b/src/bindfs.1
index 0f91a39..8a92246 100644
--- a/src/bindfs.1
+++ b/src/bindfs.1
@@ -15,15 +15,7 @@ directory. Additionally, one can change the permissions
of files in the mirrored directory.
-.SH OPTIONS
-.TP
-.B \-h, \-\-help
-Displays a help message and exits.
-
-.TP
-.B \-V, \-\-version
-Displays version information and exits.
-
+.SH FILE OWNERSHIP
.TP
.B \-u, \-\-user, \-\-owner=\fIuser\fP, \-o owner=...
Makes all files owned by the specified user.
@@ -48,7 +40,7 @@ Note that, as usual, the root user isn't bound by the permissions set here.
You can get a truly read-only mount by using \fB-r\fP.
.TP
-.B \-m, \-\-mirror=\fIusers\fP, \-o mirror=...
+.B \-m, \-\-mirror=\fIuser1:user2:...\fP, \-o mirror=...
Takes a comma\- or colon\-separated list of users who will see themselves as
the owners of all files. Users who are not listed here will still be able
to access the mount if the permissions otherwise allow them to.
@@ -57,13 +49,19 @@ You can also give a group name prefixed with an '@' to mirror all members of
a group. This will not change which group the files are shown to have.
.TP
-.B \-M, \-\-mirror\-only=\fIusers\fP, \-o mirror\-only=...
+.B \-M, \-\-mirror\-only=\fIuser1:user2:...\fP, \-o mirror\-only=...
Like \fB\-\-mirror\fP but disallows access for all other users (except root).
.TP
-.B \-n, \-\-no\-allow\-other, \-o no\-allow\-other
-Does not add \fB\-o allow_other\fP to FUSE options.
-This causes the mount to be accessible only by the current user.
+.B \-\-map=\fIuser1/user2:@group1/@group2:...\fP, \-o map=...
+Given a mapping \fIuser1/user2\fP, all files owned by user1 are shown
+as owned by user2. Additionally, when user2 creates files, they are chowned
+to user1 in the underlying directory. Works similarly for groups.
+
+A single user or group may appear no more than once on the left and once on the
+right of a slash in the list of mappings.
+Currently, the options \fB--user\fP, \fB--group\fP, \fB--mirror\fP and
+\fB--create-for-*\fP override the corresponding behavior of this option.
.SH FILE CREATION POLICY
@@ -199,6 +197,19 @@ file permissions inside the mount.
.SH MISCELLANEOUS OPTIONS
.TP
+.B \-h, \-\-help
+Displays a help message and exits.
+
+.TP
+.B \-V, \-\-version
+Displays version information and exits.
+
+.TP
+.B \-n, \-\-no\-allow\-other, \-o no\-allow\-other
+Does not add \fB\-o allow_other\fP to FUSE options.
+This causes the mount to be accessible only by the current user.
+
+.TP
.B \-\-realistic\-permissions, \-o realistic\-permissions
Hides read/write/execute permissions for a mirrored file when the mounter
doesn't have read/write/execute access to the underlying file.
diff --git a/src/bindfs.c b/src/bindfs.c
index 02f281c..24d4f53 100644
--- a/src/bindfs.c
+++ b/src/bindfs.c
@@ -64,6 +64,7 @@
#include "debug.h"
#include "permchain.h"
#include "userinfo.h"
+#include "usermap.h"
#include "misc.h"
/* SETTINGS */
@@ -77,6 +78,12 @@ static struct settings {
const char *mntsrc;
const char *mntdest;
int mntsrc_fd;
+
+ char* original_working_dir;
+ mode_t original_umask;
+
+ UserMap* usermap; /* From the --map option. */
+ UserMap* usermap_reverse;
enum CreatePolicy {
CREATE_AS_USER,
@@ -178,10 +185,13 @@ static int bindfs_fsync(const char *path, int isdatasync,
static void print_usage(const char *progname);
-static void atexit_func();
static int process_option(void *data, const char *arg, int key,
struct fuse_args *outargs);
static int parse_mirrored_users(char* mirror);
+static int parse_user_map(UserMap *map, UserMap *reverse_map, char *spec);
+static char* get_working_dir();
+static void maybe_stdout_stderr_to_file();
+static void atexit_func();
static int is_mirroring_enabled()
{
@@ -229,6 +239,10 @@ static int getattr_common(const char *procpath, struct stat *stbuf)
if (settings.ctime_from_mtime)
stbuf->st_ctime = stbuf->st_mtime;
+ /* Possibly map user/group */
+ stbuf->st_uid = usermap_get_uid(settings.usermap, stbuf->st_uid);
+ stbuf->st_gid = usermap_get_gid(settings.usermap, stbuf->st_gid);
+
/* Report user-defined owner/group if specified */
if (settings.new_uid != -1)
stbuf->st_uid = settings.new_uid;
@@ -243,26 +257,26 @@ static int getattr_common(const char *procpath, struct stat *stbuf)
return 0;
}
- if ((stbuf->st_mode & S_IFLNK) == S_IFLNK)
- return 0; /* don't bother with symlink permissions -- they don't matter */
-
- /* Apply user-defined permission bit modifications */
- stbuf->st_mode = permchain_apply(settings.permchain, stbuf->st_mode);
-
- /* Check that we can really do what we promise if --realistic-permissions was given */
- if (settings.realistic_permissions) {
- if (access(procpath, R_OK) == -1)
- stbuf->st_mode &= ~0444;
- if (access(procpath, W_OK) == -1)
- stbuf->st_mode &= ~0222;
- if (access(procpath, X_OK) == -1)
- stbuf->st_mode &= ~0111;
- }
-
/* Hide hard links */
if (settings.hide_hard_links)
stbuf->st_nlink = 1;
+ /* Then permission bits. Symlink permissions don't matter, though. */
+ if ((stbuf->st_mode & S_IFLNK) != S_IFLNK) {
+ /* Apply user-defined permission bit modifications */
+ stbuf->st_mode = permchain_apply(settings.permchain, stbuf->st_mode);
+
+ /* Check that we can really do what we promise if --realistic-permissions was given */
+ if (settings.realistic_permissions) {
+ if (access(procpath, R_OK) == -1)
+ stbuf->st_mode &= ~0444;
+ if (access(procpath, W_OK) == -1)
+ stbuf->st_mode &= ~0222;
+ if (access(procpath, X_OK) == -1)
+ stbuf->st_mode &= ~0111;
+ }
+ }
+
return 0;
}
@@ -270,6 +284,8 @@ static void *bindfs_init()
{
assert(settings.permchain != NULL);
assert(settings.mntsrc_fd > 0);
+
+ maybe_stdout_stderr_to_file();
if (fchdir(settings.mntsrc_fd) != 0) {
fprintf(
@@ -376,8 +392,8 @@ static int bindfs_mknod(const char *path, mode_t mode, dev_t rdev)
{
int res;
struct fuse_context *fc;
- uid_t file_owner = -1;
- gid_t file_group = -1;
+ uid_t file_owner;
+ gid_t file_group;
path = process_path(path);
@@ -390,11 +406,15 @@ static int bindfs_mknod(const char *path, mode_t mode, dev_t rdev)
if (res == -1)
return -errno;
+ fc = fuse_get_context();
+
if (settings.create_policy == CREATE_AS_USER) {
- fc = fuse_get_context();
file_owner = fc->uid;
file_group = fc->gid;
}
+
+ file_owner = usermap_get_uid_or_none(settings.usermap_reverse, fc->uid);
+ file_group = usermap_get_gid_or_none(settings.usermap_reverse, fc->gid);
if (settings.create_for_uid != -1)
file_owner = settings.create_for_uid;
@@ -414,8 +434,8 @@ static int bindfs_mkdir(const char *path, mode_t mode)
{
int res;
struct fuse_context *fc;
- uid_t file_owner = -1;
- gid_t file_group = -1;
+ uid_t file_owner;
+ gid_t file_group;
path = process_path(path);
@@ -426,11 +446,15 @@ static int bindfs_mkdir(const char *path, mode_t mode)
if (res == -1)
return -errno;
+ fc = fuse_get_context();
+
if (settings.create_policy == CREATE_AS_USER) {
- fc = fuse_get_context();
file_owner = fc->uid;
file_group = fc->gid;
}
+
+ file_owner = usermap_get_uid_or_none(settings.usermap_reverse, fc->uid);
+ file_group = usermap_get_gid_or_none(settings.usermap_reverse, fc->gid);
if (settings.create_for_uid != -1)
file_owner = settings.create_for_uid;
@@ -476,8 +500,8 @@ static int bindfs_symlink(const char *from, const char *to)
{
int res;
struct fuse_context *fc;
- uid_t file_owner = -1;
- gid_t file_group = -1;
+ uid_t file_owner;
+ gid_t file_group;
to = process_path(to);
@@ -485,11 +509,15 @@ static int bindfs_symlink(const char *from, const char *to)
if (res == -1)
return -errno;
+ fc = fuse_get_context();
+
if (settings.create_policy == CREATE_AS_USER) {
- fc = fuse_get_context();
file_owner = fc->uid;
file_group = fc->gid;
}
+
+ file_owner = usermap_get_uid_or_none(settings.usermap_reverse, fc->uid);
+ file_group = usermap_get_gid_or_none(settings.usermap_reverse, fc->gid);
if (settings.create_for_uid != -1)
file_owner = settings.create_for_uid;
@@ -661,8 +689,8 @@ static int bindfs_create(const char *path, mode_t mode, struct fuse_file_info *f
{
int fd;
struct fuse_context *fc;
- uid_t file_owner = -1;
- gid_t file_group = -1;
+ uid_t file_owner;
+ gid_t file_group;
path = process_path(path);
@@ -673,11 +701,15 @@ static int bindfs_create(const char *path, mode_t mode, struct fuse_file_info *f
if (fd == -1)
return -errno;
+ fc = fuse_get_context();
+
if (settings.create_policy == CREATE_AS_USER) {
- fc = fuse_get_context();
file_owner = fc->uid;
file_group = fc->gid;
}
+
+ file_owner = usermap_get_uid_or_none(settings.usermap_reverse, fc->uid);
+ file_group = usermap_get_gid_or_none(settings.usermap_reverse, fc->gid);
if (settings.create_for_uid != -1)
file_owner = settings.create_for_uid;
@@ -903,14 +935,14 @@ static void print_usage(const char *progname)
" -h --help Print this and exit.\n"
" -V --version Print version number and exit.\n"
"\n"
- "Options:\n"
+ "File ownership:\n"
" -u --user, --owner Set file owner.\n"
" -g --group Set file group.\n"
" -m --mirror Comma-separated list of users who will see\n"
" themselves as the owners of all files.\n"
" -M --mirror-only Like --mirror but disallow access for\n"
" all other users.\n"
- " -n --no-allow-other Do not add -o allow_other to fuse options.\n"
+ " --map=user1/user2:... Let user2 see files of user1 as his own.\n"
"\n"
"Permission bits:\n"
" -p --perms Specify permissions, similar to chmod\n"
@@ -945,6 +977,7 @@ static void print_usage(const char *progname)
" --xattr-rw Read-write xattr operations (the default).\n"
"\n"
"Miscellaneous:\n"
+ " -n --no-allow-other Do not add -o allow_other to fuse options.\n"
" --realistic-permissions Hide permission bits for actions mounter can't do.\n"
" --ctime-from-mtime Read file properties' change time\n"
" from file content modification time.\n"
@@ -963,18 +996,6 @@ static void print_usage(const char *progname)
}
-static void atexit_func()
-{
- permchain_destroy(settings.permchain);
- settings.permchain = NULL;
- permchain_destroy(settings.create_permchain);
- settings.create_permchain = NULL;
- free(settings.mirrored_users);
- settings.mirrored_users = NULL;
- free(settings.mirrored_members);
- settings.mirrored_members = NULL;
-}
-
enum OptionKey {
OPTKEY_NONOPTION = -2,
OPTKEY_UNKNOWN = -1,
@@ -1132,11 +1153,12 @@ static int parse_mirrored_users(char* mirror)
}
free(tmpstr);
- while (*p != '\0' && *p != ',' && *p != ':')
+ while (*p != '\0' && *p != ',' && *p != ':') {
++p;
- if (*p != '\0')
+ }
+ if (*p != '\0') {
++p;
- else {
+ } else {
/* Done. The counters should match. */
assert(i == settings.num_mirrored_users);
assert(j == settings.num_mirrored_members);
@@ -1146,6 +1168,144 @@ static int parse_mirrored_users(char* mirror)
return 1;
}
+static int parse_user_map(UserMap *map, UserMap *reverse_map, char *spec)
+{
+ char *p = spec;
+ char *tmpstr = NULL;
+ char *q;
+ uid_t uid_from, uid_to;
+ gid_t gid_from, gid_to;
+ UsermapStatus status;
+
+ while (*p != '\0') {
+ free(tmpstr);
+ tmpstr = strdup_until(p, ",:");
+
+ if (tmpstr[0] == '@') { /* group */
+ q = strstr(tmpstr, "/@");
+ if (!q) {
+ fprintf(stderr, "Invalid syntax: expected @group1/@group2 but got `%s`\n", tmpstr);
+ goto fail;
+ }
+ *q = '\0';
+ if (!group_gid(tmpstr + 1, &gid_from)) {
+ fprintf(stderr, "Invalid group: %s\n", tmpstr);
+ goto fail;
+ }
+ q += strlen("/@");
+ if (!group_gid(q, &gid_to)) {
+ fprintf(stderr, "Invalid group: %s\n", tmpstr);
+ goto fail;
+ }
+
+ status = usermap_add_gid(map, gid_from, gid_to);
+ if (status != 0) {
+ fprintf(stderr, "%s\n", usermap_errorstr(status));
+ goto fail;
+ }
+ status = usermap_add_gid(reverse_map, gid_to, gid_from);
+ if (status != 0) {
+ fprintf(stderr, "%s\n", usermap_errorstr(status));
+ goto fail;
+ }
+
+ } else {
+
+ q = strstr(tmpstr, "/");
+ if (!q) {
+ fprintf(stderr, "Invalid syntax: expected user1/user2 but got `%s`\n", tmpstr);
+ goto fail;
+ }
+ *q = '\0';
+ if (!user_uid(tmpstr, &uid_from)) {
+ fprintf(stderr, "Invalid username: %s\n", tmpstr);
+ goto fail;
+ }
+ q += strlen("/");
+ if (!user_uid(q, &uid_to)) {
+ fprintf(stderr, "Invalid username: %s\n", tmpstr);
+ goto fail;
+ }
+
+ status = usermap_add_uid(map, uid_from, uid_to);
+ if (status != 0) {
+ fprintf(stderr, "%s\n", usermap_errorstr(status));
+ goto fail;
+ }
+ status = usermap_add_uid(reverse_map, uid_to, uid_from);
+ if (status != 0) {
+ fprintf(stderr, "%s\n", usermap_errorstr(status));
+ goto fail;
+ }
+ }
+
+ while (*p != '\0' && *p != ',' && *p != ':') {
+ ++p;
+ }
+ if (*p != '\0') {
+ ++p;
+ }
+ }
+
+ free(tmpstr);
+ return 1;
+
+fail:
+ free(tmpstr);
+ return 0;
+}
+
+static void maybe_stdout_stderr_to_file()
+{
+ /* TODO: make this a command line option. */
+#if 0
+ int fd;
+
+ const char *filename = "bindfs.log";
+ char *path = malloc(strlen(settings.original_working_dir) + 1 + strlen(filename) + 1);
+ strcpy(path, settings.original_working_dir);
+ strcat(path, "/");
+ strcat(path, filename);
+
+ fd = open(path, O_CREAT | O_WRONLY);
+ free(path);
+
+ fchmod(fd, 0777 & ~settings.original_umask);
+ fflush(stdout);
+ fflush(stderr);
+ dup2(fd, 1);
+ dup2(fd, 2);
+#endif
+}
+
+static char* get_working_dir()
+{
+ size_t buf_size = 4096;
+ char* buf = malloc(buf_size);
+ while (!getcwd(buf, buf_size)) {
+ buf_size *= 2;
+ buf = realloc(buf, buf_size);
+ }
+ return buf;
+}
+
+static void atexit_func()
+{
+ free(settings.original_working_dir);
+ settings.original_working_dir = NULL;
+ usermap_destroy(settings.usermap);
+ settings.usermap = NULL;
+ usermap_destroy(settings.usermap_reverse);
+ settings.usermap_reverse = NULL;
+ permchain_destroy(settings.permchain);
+ settings.permchain = NULL;
+ permchain_destroy(settings.create_permchain);
+ settings.create_permchain = NULL;
+ free(settings.mirrored_users);
+ settings.mirrored_users = NULL;
+ free(settings.mirrored_members);
+ settings.mirrored_members = NULL;
+}
int main(int argc, char *argv[])
{
@@ -1158,6 +1318,7 @@ int main(int argc, char *argv[])
char *perms;
char *mirror;
char *mirror_only;
+ char *map;
char *create_for_user;
char *create_for_group;
char *create_with_perms;
@@ -1183,6 +1344,7 @@ int main(int argc, char *argv[])
OPT_OFFSET3("-p %s", "--perms=%s", "perms=%s", perms, -1),
OPT_OFFSET3("-m %s", "--mirror=%s", "mirror=%s", mirror, -1),
OPT_OFFSET3("-M %s", "--mirror-only=%s", "mirror-only=%s", mirror_only, -1),
+ OPT_OFFSET2("--map=%s", "map=%s", map, -1),
OPT_OFFSET3("-n", "--no-allow-other", "no-allow-other", no_allow_other, -1),
OPT2("--create-as-user", "create-as-user", OPTKEY_CREATE_AS_USER),
OPT2("--create-as-mounter", "create-as-mounter", OPTKEY_CREATE_AS_MOUNTER),
@@ -1215,12 +1377,15 @@ int main(int argc, char *argv[])
memset(&od, 0, sizeof(od));
settings.progname = argv[0];
settings.permchain = permchain_create();
+ settings.usermap = usermap_create();
+ settings.usermap_reverse = usermap_create();
settings.new_uid = -1;
settings.new_gid = -1;
settings.create_for_uid = -1;
settings.create_for_gid = -1;
settings.mntsrc = NULL;
settings.mntdest = NULL;
+ settings.original_working_dir = get_working_dir();
settings.create_policy = (getuid() == 0) ? CREATE_AS_USER : CREATE_AS_MOUNTER;
settings.create_permchain = permchain_create();
settings.chown_policy = CHOWN_NORMAL;
@@ -1237,7 +1402,7 @@ int main(int argc, char *argv[])
settings.ctime_from_mtime = 0;
settings.hide_hard_links = 0;
atexit(&atexit_func);
-
+
/* Parse options */
if (fuse_opt_parse(&args, &od, options, &process_option) == -1)
return 1;
@@ -1261,6 +1426,14 @@ int main(int argc, char *argv[])
return 1;
}
}
+
+ /* Parse usermap */
+ if (od.map) {
+ if (!parse_user_map(settings.usermap, settings.usermap_reverse, od.map)) {
+ /* parse_user_map printed an error */
+ return 1;
+ }
+ }
/* Parse user and group for new creates */
if (od.create_for_user) {
@@ -1341,7 +1514,7 @@ int main(int argc, char *argv[])
}
/* Ignore the umask of the mounter on file creation */
- umask(0);
+ settings.original_umask = umask(0);
/* Remove xattr implementation if the user doesn't want it */
if (settings.xattr_policy == XATTR_UNIMPLEMENTED) {
diff --git a/src/usermap.c b/src/usermap.c
new file mode 100644
index 0000000..b900296
--- /dev/null
+++ b/src/usermap.c
@@ -0,0 +1,123 @@
+
+#include "usermap.h"
+#include "userinfo.h"
+#include <stdlib.h>
+
+struct UserMap {
+ uid_t *user_from;
+ uid_t *user_to;
+ gid_t *group_from;
+ gid_t *group_to;
+ int user_capacity;
+ int group_capacity;
+ int user_size;
+ int group_size;
+};
+
+UserMap *usermap_create()
+{
+ UserMap* map = (UserMap*)malloc(sizeof(UserMap));
+ map->user_from = NULL;
+ map->user_to = NULL;
+ map->group_from = NULL;
+ map->group_to = NULL;
+ map->user_capacity = 0;
+ map->group_capacity = 0;
+ map->user_size = 0;
+ map->group_size = 0;
+ return map;
+}
+
+void usermap_destroy(UserMap *map)
+{
+ free(map->user_from);
+ free(map->user_to);
+ free(map->group_from);
+ free(map->group_to);
+ free(map);
+}
+
+UsermapStatus usermap_add_uid(UserMap *map, uid_t from, uid_t to)
+{
+ int i;
+ if (from == to) {
+ return usermap_status_ok;
+ }
+ if (map->user_size == map->user_capacity) {
+ map->user_capacity *= 2;
+ map->user_from = (uid_t*)realloc(map->user_from, map->user_capacity * sizeof(uid_t));
+ map->user_to = (uid_t*)realloc(map->user_to, map->user_capacity * sizeof(uid_t));
+ }
+ if (usermap_get_uid(map, from) != from) {
+ return usermap_status_duplicate_key;
+ }
+ i = map->user_size;
+ map->user_from[i] = from;
+ map->user_to[i] = to;
+ map->user_size += 1;
+ return usermap_status_ok;
+}
+
+UsermapStatus usermap_add_gid(UserMap *map, gid_t from, gid_t to)
+{
+ int i;
+ if (from == to) {
+ return usermap_status_ok;
+ }
+ if (map->group_size == map->group_capacity) {
+ map->group_capacity *= 2;
+ map->group_from = (gid_t*)realloc(map->group_from, map->group_capacity * sizeof(gid_t));
+ map->group_to = (gid_t*)realloc(map->group_to, map->group_capacity * sizeof(gid_t));
+ }
+ if (usermap_get_gid(map, from) != from) {
+ return usermap_status_duplicate_key;
+ }
+ i = map->group_size;
+ map->group_from[i] = from;
+ map->group_to[i] = to;
+ map->group_size += 1;
+ return usermap_status_ok;
+}
+
+const char* usermap_errorstr(UsermapStatus status)
+{
+ switch (status) {
+ case usermap_status_ok: return "ok";
+ case usermap_status_duplicate_key: return "user mapped twice";
+ default: return "unknown error";
+ }
+}
+
+uid_t usermap_get_uid(UserMap *map, uid_t u)
+{
+ uid_t result = usermap_get_uid_or_none(map, u);
+ return (result != -1 ? result : u);
+}
+
+gid_t usermap_get_gid(UserMap *map, gid_t g)
+{
+ gid_t result = usermap_get_gid_or_none(map, g);
+ return (result != -1 ? result : g);
+}
+
+uid_t usermap_get_uid_or_none(UserMap *map, uid_t u)
+{
+ int i;
+ for (i = 0; i < map->user_size; ++i) {
+ if (map->user_from[i] == u) {
+ return map->user_to[i];
+ }
+ }
+ return -1;
+}
+
+gid_t usermap_get_gid_or_none(UserMap *map, gid_t g)
+{
+ int i;
+ for (i = 0; i < map->group_size; ++i) {
+ if (map->group_from[i] == g) {
+ return map->group_to[i];
+ }
+ }
+ return -1;
+}
diff --git a/src/usermap.h b/src/usermap.h
new file mode 100644
index 0000000..8a74bb1
--- /dev/null
+++ b/src/usermap.h
@@ -0,0 +1,60 @@
+/*
+ Copyright 2012 Martin Pärtel <martin.partel@gmail.com>
+
+ This file is part of bindfs.
+
+ bindfs is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ bindfs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INC_BINDFS_USERMAP_H
+#define INC_BINDFS_USERMAP_H
+
+#include <config.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+/* A map of user IDs to userIDs and group IDs to group IDs. */
+struct UserMap;
+typedef struct UserMap UserMap;
+
+typedef enum UsermapStatus {
+ usermap_status_ok = 0,
+ usermap_status_duplicate_key = 1
+} UsermapStatus;
+
+UserMap *usermap_create();
+void usermap_destroy(UserMap *map);
+
+UsermapStatus usermap_add_uid(UserMap *map, uid_t from, uid_t to);
+UsermapStatus usermap_add_gid(UserMap *map, gid_t from, gid_t to);
+
+const char* usermap_errorstr(UsermapStatus status);
+
+/* Returns the uid that u is mapped to, or u if none. */
+uid_t usermap_get_uid(UserMap *map, uid_t u);
+
+/* Returns the gid that g is mapped to, or g if none. */
+gid_t usermap_get_gid(UserMap *map, gid_t g);
+
+/* Returns the uid that u is mapped to, or -1 if none. */
+uid_t usermap_get_uid_or_none(UserMap *map, uid_t u);
+
+/* Returns the gid that g is mapped to, or -1 if none. */
+gid_t usermap_get_gid_or_none(UserMap *map, gid_t g);
+
+#endif