/*
 * 2007+ Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
 * All rights reserved.
 *
 * 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.
 */

#define _GNU_SOURCE
#define __USE_FILE_OFFSET64
#define __USE_LARGEFILE64
#define _FILE_OFFSET_BITS	64

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sendfile.h>

#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <pthread.h>
#include <poll.h>
#include <signal.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <netinet/in.h>
#include <netinet/tcp.h>

#include <asm/byteorder.h>

#include <fs/pohmelfs/netfs.h>

#include <openssl/hmac.h>
#include <openssl/evp.h>

#include "list.h"
#include "rbtree.h"
#include "jhash.h"

#define uloga(f, a...) fprintf(stderr, f, ##a)
#define ulog_err(f, a...) uloga(f ": %s [%d].\n", ##a, strerror(errno), errno)

#define FLUSH_DEBUG

#ifdef FLUSH_DEBUG
#define ulog(f, a...) uloga(f, ##a)
#else
#define ulog(f, a...) do {} while (0)
#endif

#define __unused		 __attribute__ ((unused))
#define NIP6(addr) \
	ntohs((addr).s6_addr16[0]), \
	ntohs((addr).s6_addr16[1]), \
	ntohs((addr).s6_addr16[2]), \
	ntohs((addr).s6_addr16[3]), \
	ntohs((addr).s6_addr16[4]), \
	ntohs((addr).s6_addr16[5]), \
	ntohs((addr).s6_addr16[6]), \
	ntohs((addr).s6_addr16[7])
#define NIP6_FMT "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x"

static void flush_usage(char *p)
{
	uloga("Usage: %s -6 -a addr -p port -f path.\n", p);
}

static int flush_init_socket(int family, char *addr, char *port)
{
	int s, err;
	struct addrinfo *ai, hint;

	s = socket(family, SOCK_STREAM, IPPROTO_TCP);
	if (s < 0) {
		ulog_err("Failed to create TCP socket");
		return -1;
	}

	memset(&hint, 0, sizeof(struct addrinfo));

	hint.ai_flags = AI_NUMERICSERV;
	hint.ai_family = family;
	hint.ai_socktype = SOCK_STREAM;
	hint.ai_protocol = IPPROTO_TCP;

	err = getaddrinfo(addr, port, &hint, &ai);
	if (err) {
		ulog_err("Failed to get address info for %s:%s, err: %d", addr, port, err);
		goto err_out_close;
	}

	err = connect(s, ai->ai_addr, ai->ai_addrlen);
	if (err) {
		ulog_err("Failed to connect to %s:%s", addr, port);
		goto err_out_free;
	}

	freeaddrinfo(ai);

	uloga("Connected to server %s:%s.\n", addr, port);

	return s;

err_out_free:
	freeaddrinfo(ai);
err_out_close:
	close(s);
	return -1;

}

static int flush_path(int s, char *path)
{
	unsigned int path_len = strlen(path) + 1; /* 0-byte incuded */
	unsigned int data_size = sizeof(struct netfs_lock) + sizeof(struct netfs_inode_info) + path_len;
	char buf[sizeof(struct netfs_cmd) + data_size];
	struct netfs_cmd *cmd;
	struct netfs_inode_info *info;
	struct netfs_lock *lock;
	int err;

	memset(buf, 0, sizeof(buf));

	cmd = (struct netfs_cmd *)buf;

	cmd->cmd = NETFS_LOCK;
	cmd->ext = path_len - 1;
	cmd->size = path_len + sizeof(struct netfs_lock);

	sprintf((char *)cmd->data, "%s", path);

	lock = (struct netfs_lock *)(cmd->data + cmd->ext);

	lock->type = POHMELFS_WRITE_LOCK | POHMELFS_LOCK_GRAB;
	lock->start = 0;
	lock->size = ~0;
	lock->ino = 0;

	netfs_convert_cmd(cmd);
	netfs_convert_lock(lock);

	err = send(s, cmd, sizeof(struct netfs_cmd) + path_len + sizeof(struct netfs_lock), 0);
	if (err <= 0) {
		ulog_err("Failed to send flush command to the server");
		return err;
	}

	err = recv(s, buf, sizeof(buf), 0);
	if (err <= 0) {
		ulog_err("Failed to receive flush command response");
		return err;
	}

	netfs_convert_cmd(cmd);

	info = (struct netfs_inode_info *)(cmd + 1);
	netfs_convert_inode_info(info);

	uloga("%s: flushed: size: %llu, ino: %llu, uid: %u, gid: %u, mode: %o.\n",
			path, info->size, info->ino, info->uid, info->gid, info->mode);
	return 0;
}

int main(int argc, char *argv[])
{
	int ch, s, family;
	char *addr, *port, *path;

	addr = port = path = NULL;
	family = AF_INET;

	while ((ch = getopt(argc, argv, "6:a:p:f:h")) != -1) {
		switch (ch) {
			case '6':
				family = AF_INET6;
				break;
			case 'a':
				addr = optarg;
				break;
			case 'p':
				port = optarg;
				break;
			case 'f':
				path = optarg;
				break;
			default:
				flush_usage(argv[0]);
				return -1;
		}
	}

	if (!addr || !port || !path) {
		uloga("You have to provide address, port and object path.\n");
		flush_usage(argv[0]);
		return -1;
	}

	s = flush_init_socket(family, addr, port);
	if (s <= 0)
		return s;

	return flush_path(s, path);
}
