/*
 *  FL-COW by Davide Libenzi ( File Links Copy On Write )
 *  Copyright (C) 2003  Davide Libenzi
 *
 *  This program 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.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Davide Libenzi <davidel@xmailserver.org>
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/mman.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <dlfcn.h>



#ifdef ENABLE_DEBUG
#define DPRINTF(format, args...) fprintf(stderr, format, ## args)
#else
#define DPRINTF(format, args...)
#endif


#ifndef RTLD_NEXT
#define RTLD_NEXT ((void *) -1L)
#endif

#define REAL_LIBC RTLD_NEXT

#define FLCLOW_MAXPATH 1024

#define FLCOW_ALIAS(asym, rsym) int asym(const char *, int, ...) __attribute__ ((weak, alias("flcow_" #rsym)))
#define FLCOW_MAPFUNC(sym) \
	int flcow_##sym(const char *name, int flags, ...) { \
		static int (*func_open)(const char *, int, ...) = NULL; \
		va_list args; \
		mode_t mode; \
		if (!func_open && \
			    !(func_open = (int (*)(const char *, int, ...)) \
				      dlsym(REAL_LIBC, #sym))) { \
			fprintf(stderr, "missing symbol: %s\n", #sym); \
			exit(1); \
		} \
		va_start(args, flags); \
		mode = va_arg(args, mode_t); \
		va_end(args); \
		DPRINTF("%s[%p](%s, 0x%x, 0x%x)\n", __FUNCTION__, flcow_##sym, name, \
				flags, mode); \
		return do_generic_open(name, flags, mode, func_open); \
	} \
	FLCOW_ALIAS(sym, sym)



static int cow_name(const char *name) {
	int nlen, len;
	char const *home, *excld, *next;
	char fpath[FLCLOW_MAXPATH];

	nlen = strlen(name);
	if (*name != '/' && nlen < sizeof(fpath) - 1) {
		if (name[0] == '~' && name[1] == '/') {
			if ((home = getenv("HOME")) != NULL) {
				strncpy(fpath, home, sizeof(fpath) - 1);
				name += 2;
				nlen -= 2;
			} else
				fpath[0] = '\0';
		} else {
			if (!getcwd(fpath, sizeof(fpath) - 1 - nlen))
				fpath[0] = '\0';
		}
		if ((len = strlen(fpath)) + nlen + 2 < sizeof(fpath)) {
			if (len && fpath[len - 1] != '/')
				fpath[len++] = '/';
			memcpy(fpath + len, name, nlen + 1);
			name = fpath;
			nlen += len;
		}
	}
	for (excld = getenv("FLCOW_PATH"); excld;) {
		if (!(next = strchr(excld, ':')))
			len = strlen(excld);
		else
			len = next - excld;
		if (len && !strncmp(excld, name, len))
			return 1;
		excld = next ? next + 1: NULL;
	}
	return 0;
}


static int do_cow_name(const char *name, int (*open_proc)(const char *, int, ...)) {
	int nfd, sfd;
	void *addr;
	struct stat stb;
	char fpath[FLCLOW_MAXPATH];

	if ((sfd = open_proc(name, O_RDONLY, 0)) == -1)
		return -1;
	if (fstat(sfd, &stb)) {
		close(sfd);
		return -1;
	}
	snprintf(fpath, sizeof(fpath) - 1, "%s,,+++", name);
	if ((nfd = open_proc(fpath, O_CREAT | O_EXCL | O_WRONLY, stb.st_mode)) == -1) {
		close(sfd);
		return -1;
	}
	if ((addr = mmap(NULL, stb.st_size, PROT_READ, MAP_PRIVATE,
			 sfd, 0)) == MAP_FAILED) {
		close(nfd);
		unlink(fpath);
		close(sfd);
		return -1;
	}
	if (write(nfd, addr, stb.st_size) != stb.st_size) {
		munmap(addr, stb.st_size);
		close(nfd);
		unlink(fpath);
		close(sfd);
		return -1;
	}
	munmap(addr, stb.st_size);
	close(sfd);
	fchown(nfd, stb.st_uid, stb.st_gid);
	close(nfd);

	if (unlink(name)) {
		unlink(fpath);
		return -1;
	}

	rename(fpath, name);

	return 0;
}


static int do_generic_open(const char *name, int flags, mode_t mode,
			   int (*open_proc)(const char *, int, ...)) {
	struct stat stb;

	if ((flags & O_RDWR || flags & O_WRONLY) && cow_name(name) &&
	    !stat(name, &stb) && S_ISREG(stb.st_mode) && stb.st_nlink > 1) {
		DPRINTF("COW-ing %s\n", name);
		if (do_cow_name(name, open_proc) < 0) {
			DPRINTF("COW-ing %s failed !\n", name);
		} else {
			DPRINTF("COW-ing %s succeed\n", name);
		}
	}
	DPRINTF("calling libc open[%p](%s, 0x%x, 0x%x)\n", open_proc, name, flags, mode);

	return open_proc(name, flags, mode);
}


#include "fl-cow-config.h"

/*
FLCOW_MAPFUNC(__open);
FLCOW_MAPFUNC(__libc_open64);
FLCOW_ALIAS(open, __open);
FLCOW_ALIAS(open64, __libc_open64);
*/

