/*
 * Copyright (c) 2003 Olaf Dietsche
 *
 * Filesystem capabilities for linux, user space tool.
 *
 * This tool uses libcap. Compile as:
 * gcc -Wall -o lscap lscap.c -lcap
 *
 * usage: lscap file ...
 * example: lscap /bin/ping
 *
 * Thanks to Steve Baur for the idea to locate_mountpoint().
 */

#include <asm/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/capability.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define TRACE fprintf(stderr, "%s(%d)\n", __FILE__, __LINE__)

static void fatal(const char *s)
{
	perror(s);
	exit(1);
}

static char *locate_mountpoint(char *dir, const char *file)
{
	char *slash;
	struct stat buf;
	dev_t dev;
	int err = stat(file, &buf);
	if (err)
		return NULL;

	dev = buf.st_dev;
	strcpy(dir, file);
	do {
		slash = strrchr(dir, '/');
		*slash = 0;
		err = stat(slash == dir ? "/" : dir, &buf);
		if (err)
			return NULL;
	} while (buf.st_dev == dev && slash != dir);

	*slash = '/';
	if (buf.st_dev == dev)
		dir[1] = 0;

	return dir;
}

static int open_capabilities(const char *name)
{
	char file[PATH_MAX], mnt[PATH_MAX];
	if (!realpath(name, file))
		return -1;

	if (!locate_mountpoint(mnt, file))
		return -1;

	strcat(mnt, "/.capabilities");
	return open(mnt, O_RDONLY, 0666);
}

static void lscap(const char *name, __u32 *caps)
{
	struct stat buf;
	int fd = open_capabilities(name);
	if (fd < 0) {
		perror(name);
		return;
	}

	if (access(name, R_OK) || stat(name, &buf)) {
		perror(name);
	} else {
		__u32 set[3][4];
		off_t rc = lseek(fd, buf.st_ino * sizeof(set), SEEK_SET);
		if (rc == (off_t) -1)
			perror(name);
		else {
			int n = read(fd, set, sizeof(set));
			switch (n) {
			case -1:
				perror(name);
				break;
			case 0:
				caps[0] = caps[1] = caps[2] = 0;
				break;
			default:
				caps[0] = set[0][0];
				caps[1] = set[1][0];
				caps[2] = set[2][0];
				break;
			}
		}
	}

	close(fd);
}

static void cap_from_u32(cap_t cap, __u32 c, cap_flag_t flag)
{
	int i;
	for (i = 0; i <= 28; ++i) {
		cap_value_t caps = i;
		int err = cap_set_flag(cap, flag, 1, &caps, c & (1 << i) ? CAP_SET : CAP_CLEAR);
		if (err)
			fatal("cap_from_u32()");
	}
}

int main(int argc, char **argv)
{
	int i;

	if (argc < 2) {
		fprintf(stderr, "usage: %s file ...\n", argv[0]);
		exit(2);
	}

	for (i = 1; i < argc; ++i) {
		const char *s;
		cap_t cap = cap_init();
		__u32 caps[3];
		lscap(argv[i], caps);
//		cap_clear(cap);
		cap_from_u32(cap, caps[0], CAP_EFFECTIVE);
		cap_from_u32(cap, caps[1], CAP_INHERITABLE);
		cap_from_u32(cap, caps[2], CAP_PERMITTED);

		s = cap_to_text(cap, 0);
		printf("%s: %s\n", argv[i], s);
		cap_free(cap);
	}

	return 0;
}
