/*
 * gfvoms-update.c
 *
 * Copyright (c) 2009 National Institute of Informatics in Japan.
 * All rights reserved.
 */

#include <ctype.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <gfarm/gfarm.h>

#include "gfm_client.h"
#include "lookup.h"
#include "config.h"

#define MAX_LINE_LEN	4096
#define USER_ALLOC_SIZE	20
#define VOID_FORM		"void="
#define VO_FORM			"vo="
#define GROUP_FORM		"voms="
#define USER_FORM		"user="

#define OP_VOMS_FILE	'm'
#define OP_VOMSID		'V'
#define OP_VO			'v'
#define OP_NO_UPDATE	'n'
#define OP_USAGE		'h'
#define OP_DELETE		'd'
#define OP_ADD			1
#define OP_DEL			2
#define OP_MOD			3

char *program_name = "gfvoms-update";
struct gfm_connection *gfm_server = NULL;

struct gfarm_user_info *users = NULL;
int ngroups = 0;
struct gfarm_group_info *groups = NULL;
int nusers = 0;

char *vomsid = NULL;
char *vo = NULL;
char *vomsfile = NULL;

#define SET_VOMS_MEMBER_FILE(arg, e) \
do {\
	vomsfile = strdup((arg));\
	if (vomsfile == NULL) {\
		(e) = GFARM_ERR_NO_MEMORY;\
	}\
} while(0)

#define SET_VO(arg, e) \
do {\
	GFARM_CALLOC_ARRAY(vo, (strlen(arg) + 2));\
	if (vo == NULL) {\
		(e) = GFARM_ERR_NO_MEMORY;\
	}\
	else {\
		memset(vo, '\0', (strlen(arg) + 2));\
		sprintf(vo, "/%s", (arg));\
	}\
} while(0)

#define SET_VOMSID(arg, e) \
do {\
	vomsid = strdup((arg));\
	if (vomsid == NULL) {\
		(e) = GFARM_ERR_NO_MEMORY;\
	}\
} while(0)

/* doubly-circularly-linked list for group */
struct group_info_list {
	struct group_info_list *next;
	struct group_info_list *pre;

	char *group_name;
	struct gfarm_group_info *group_info;
	struct gfarm_user_info **user_infos;
	int nusers;
};

static void
usage(void)
{
	fprintf(stderr,
		"Usage: %s [-n] [-h] [-d] -V vomsID -v vo -m member-list\n",
		                                                 program_name);
	fprintf(stderr, "     -h            : Show this usage.\n");
	fprintf(stderr,
		"     -d            : Delete target vo groups from Gfarm.\n");
	fprintf(stderr,
		"     -V vomsID     : ID which defined in Definition file\n");
	fprintf(stderr, "     -v vo         : VO name which you want to sync\n");
	fprintf(stderr,
		"     -m member-list: MemberShip list generated by gfvoms-list\n");
	fprintf(stderr, "     -n            : Debug option. No update occurs.\n");
}

/* delete space, CR and LF from head and tail of the string.*/
static int
trim(char *str, int len)
{
	int i, j;
	for (i = 0; i < len; i++) {
		if ((!isspace(str[i]) && str[i] != '\n' && str[i] != '\r')
			                                    || str[i] == '\0') {
			break;
		}
	}
	if (i > 0) {
		for (j = 0; j < len - i; j++) {
			str[j] = str[i+j];
		}
		for (; j < len; j++) {
			str[j] = '\0';
		}
	}
	i = len - i - 1;
	while (i >= 0 && isspace(str[i])) {
		str[i] = '\0';
		i--;
	}
	while (strlen(str) > 0 &&
		  (str[strlen(str) - 1] == '\n' || str[strlen(str) - 1] == '\r')) {
		str[strlen(str) - 1] = '\0';
	}

	return 0;
}

/* get groups from gfarm */
static gfarm_error_t
list_groups(void)
{
	struct gfarm_group_info *g = NULL;
	gfarm_error_t e = GFARM_ERR_NO_ERROR;
	int n;

	e = gfm_client_group_info_get_all(gfm_server, &n, &g);
	if (e == GFARM_ERR_NO_ERROR) {
		groups = g;
		ngroups = n;
	}
	return (e);
}

/* get users from gfarm */
static gfarm_error_t
list_users(void)
{
	struct gfarm_user_info *u = NULL;
	gfarm_error_t e = GFARM_ERR_NO_ERROR;
	int n;

	e = gfm_client_user_info_get_all(gfm_server, &n, &u);
	if (e == GFARM_ERR_NO_ERROR) {
		users = u;
		nusers = n;
	}
	return (e);
}

/* get user by name */
static struct gfarm_user_info *
get_user_info_by_name(char* name)
{
	int i;
	for (i = 0; i < nusers; i++) {
		if (strcmp(name, users[i].username) == 0) {
			return &(users[i]);
		}
	}
	return NULL;
}

/* get user by dn */
static struct gfarm_user_info *
get_user_info_by_dn(char* dn)
{
	int i;
	while (isspace(*dn) && *dn != '\n' && *dn != '\0') {
	    dn++;
	}
	for (i = 0; i < nusers; i++) {
		if (strcmp(dn, users[i].gsi_dn) == 0) {
			return &(users[i]);
		}
	}
	return NULL;
}

/* free one group info */
static void
free_group_info(struct group_info_list *info)
{
	if (info == NULL) return;
	if (info->user_infos) free(info->user_infos);
	if (info->group_name) free(info->group_name);
	info->pre->next = info->next;
	info->next->pre = info->pre;
	free(info);
}

/* free all group info */
static void
free_group_info_list(struct group_info_list *list)
{
	while ((list != NULL) && (list->next != list)) {
		free_group_info(list->next);
	}
	if (list) free_group_info(list);
}

/* make dummy node for doubly-circularly-linked list */
static gfarm_error_t
make_list_head(struct group_info_list **head)
{
	gfarm_error_t e = GFARM_ERR_NO_ERROR;
	struct group_info_list *tmp = NULL;

	GFARM_MALLOC(tmp);
	if (tmp == NULL) {
		e = GFARM_ERR_NO_MEMORY;
		goto err;
	}
	tmp->next = tmp;
	tmp->pre = tmp;
	tmp->group_name = NULL;
	tmp->group_info = NULL;
	tmp->user_infos = NULL;
	tmp->nusers = 0;

	*head = tmp;
	return (e);
err:
	if (tmp) free(tmp);
	return (e);
}

/* remove node from doubly-circularly-linked list */
static struct group_info_list *
remove_list(struct group_info_list *target)
{
	struct group_info_list *tmp = NULL;
	if (target == NULL) return target;
	tmp = target->pre;

	target->pre->next = target->next;
	target->next->pre = target->pre;
	target->next = target;
	target->pre = target;
	free_group_info(target);

	return tmp;
}

/* add node to doubly-circularly-linked list */
static struct group_info_list *
add_list(struct group_info_list *target, struct group_info_list *add)
{
	struct group_info_list *tmp = NULL;

	if (target == NULL || add == NULL) return add;

	tmp = add->pre;
	add->next->pre = add->pre;
	add->pre->next = add->next;
	target->next->pre = add;
	add->next = target->next;
	add->pre = target;
	target->next = add;

	return tmp;
}

/* make new node and add it to doubly-circularly-linked list */
static gfarm_error_t
set_new_list(struct group_info_list *pre, struct group_info_list **add,
	         int nuser)
{
	gfarm_error_t e = GFARM_ERR_NO_ERROR;
	struct group_info_list *tmp = NULL;
	GFARM_MALLOC(tmp);
	if (tmp == NULL) {
		e = GFARM_ERR_NO_MEMORY;
		goto err;
	}
	tmp->next = pre->next;
	tmp->pre = pre;
	pre->next = tmp;
	tmp->next->pre = tmp;

	tmp->nusers = nuser;
	tmp->group_name = NULL;
	tmp->group_info = NULL;
	tmp->user_infos = NULL;
	GFARM_CALLOC_ARRAY(tmp->user_infos, nuser);
	if (tmp->user_infos == NULL) {
		e = GFARM_ERR_NO_MEMORY;
		goto err;
	}
	*add = tmp;
	return (e);
err:
	if (tmp) {
		if (tmp->user_infos) free(tmp->user_infos);
		free(tmp);
	}
	return (e);
}

/* get gfarm membership list */
static gfarm_error_t
make_gfarm_membership_list(struct group_info_list **gfarm_list)
{
	gfarm_error_t e = GFARM_ERR_NO_ERROR;
	struct group_info_list *tmp1 = NULL, *tmp2 = NULL;
	int i, j;
	char *p = NULL, *p2 = NULL;

	e = make_list_head(&tmp1);
	if (e != GFARM_ERR_NO_ERROR) {
		goto err;
	}

	for (i = 0; i < ngroups; i++) {
		/* check VomsID */
		if ((p = strstr(groups[i].groupname, vomsid)) == NULL) {
			continue;
		}
		if (p != groups[i].groupname || p[strlen(vomsid)] != ':') {
			continue;
		}
		/* check VO name */
		p += (strlen(vomsid) + 1);
		if ((p2 = strstr(p, vo)) == NULL || p2 != p) {
			continue;
		}
		p += (strlen(vo));
		if (*p != '\0' && *p != '/') {
			continue;
		}

		e = set_new_list(tmp1, &tmp2, groups[i].nusers);
		if (e != GFARM_ERR_NO_ERROR) {
			goto err;
		}
		tmp2->group_name = strdup(groups[i].groupname);
		if (tmp2->group_name == NULL) {
			e = GFARM_ERR_NO_MEMORY;
			goto err;
		}
		tmp2->group_info = &(groups[i]);

		for (j = 0; j < groups[i].nusers; j++) {
			tmp2->user_infos[j] = get_user_info_by_name(
				                         groups[i].usernames[j]);
			if (tmp2->user_infos[j] == NULL) {
				goto err;
			}
		}
		tmp1 = tmp2;
		tmp2 = NULL;
	}
	*gfarm_list = tmp1->next;
	return (e);
err:
	if (e == GFARM_ERR_NO_ERROR) {
		e = -1;
	}
	free_group_info_list(tmp1);
	return (e);
}

/* make group name by vomsid, vo, voms group and voms role */
static char *
make_groupname(char *group)
{
	char *ret = NULL;
	int len = strlen(vomsid) + 1 + strlen(vo) + strlen(group) + 1;

	GFARM_CALLOC_ARRAY(ret, len);
	if (ret == NULL) {
		goto err;
	}
	memset(ret, '\0', len);
	sprintf(ret, "%s:%s%s", vomsid, vo, group);

	return ret;
err:
	if (ret) free(ret);
	return NULL;
}

/* make voms membership list */
static gfarm_error_t
make_voms_membership_list(FILE *vomsfp, struct group_info_list **voms_list)
{
	gfarm_error_t e = GFARM_ERR_NO_ERROR;
	struct group_info_list *tmp1 = NULL, *tmp2 = NULL;
	char buf[MAX_LINE_LEN];
	char *idx = NULL;
	int isHead = 2, flag = 0, allocated_size = 0, i;
	int	line = 0;

	char *p = NULL;

	struct gfarm_user_info **user_tmp = NULL;

	e = make_list_head(&tmp1);
	if (e != GFARM_ERR_NO_ERROR) {
		goto err;
	}
	tmp2 = tmp1;

	memset(buf, '\0', MAX_LINE_LEN);
	while(fgets(buf, MAX_LINE_LEN, vomsfp)) {
		line++;
		idx = buf;
		if (idx[strlen(idx)-1] != '\n' && idx[strlen(idx)-1] != '\r') {
			fprintf(stderr,
				"Error:In voms membership file('%s'[line %d])\n",
				vomsfile, line);
			fprintf(stderr,
				"Line was too long. Limit length is %d.\n",
				MAX_LINE_LEN);
			goto err;
		}
		trim(idx, MAX_LINE_LEN);
		if (strlen(idx) == 0 || idx[0] == '#') continue;
		if (isHead > 0) {
			if ((p = strstr(idx, VOID_FORM)) != NULL && p == idx) {
				if (flag == 1) {
					fprintf(stderr,
						"Error:In voms membership file('%s'[line %d])\n",
						vomsfile, line);
					fprintf(stderr, "Duplicate header '%s'.\n", VOID_FORM);
					goto err;
				}
				p += strlen(VOID_FORM);
				if (strcmp(p, vomsid)) {
					fprintf(stderr,
						"Error:In voms membership file('%s'[line %d])\n",
						vomsfile, line);
					fprintf(stderr,
						"VOMSID Contradiction:vomsfile(%s) and command(%s)\n",
						p, vomsid);
					goto err;
				}
				flag = 1;
			}
			else if ((p = strstr(idx, VO_FORM)) != NULL && p == idx) {
				if (flag == 2) {
					fprintf(stderr,
						"Error:In voms membership file('%s'[line %d])\n",
						vomsfile, line);
					fprintf(stderr, "Duplicate header '%s'.\n", VO_FORM);
					goto err;
				}
				p += strlen(VO_FORM);
				if (strcmp(p, vo)) {
					fprintf(stderr,
						"Error:In voms membership file('%s'[line %d])\n",
						vomsfile, line);
					fprintf(stderr,
						"VO Contradiction:voms_file(%s) and command(%s)\n",
						p+1, vo+1);
					goto err;
				}
				flag = 2;
			}
			else {
				fprintf(stderr,
					"Error:In voms membership file('%s'[line %d])\n",
					vomsfile, line);
				fprintf(stderr, "Unknown header:%s\n", idx);
				goto err;
			}
			isHead--;
		}
		else {
			if ((p = strstr(idx, GROUP_FORM)) != NULL && p == idx) {
				tmp1 = tmp2;
				tmp2 = NULL;
				e = set_new_list(tmp1, &tmp2, USER_ALLOC_SIZE);
				if (e != GFARM_ERR_NO_ERROR) {
					fprintf(stderr,
						"Error:In voms membership file('%s'[line %d])\n",
						vomsfile, line);
					goto err;
				}
				tmp2->nusers = 0;
				allocated_size = USER_ALLOC_SIZE;

				p += strlen(GROUP_FORM);
				tmp2->group_name = make_groupname(p);
				if (tmp2->group_name == NULL) {
					fprintf(stderr,
						"Error:In voms membership file('%s'[line %d])\n",
						vomsfile, line);
					e = GFARM_ERR_NO_MEMORY;
					goto err;
				}
			}
			else if ((p = strstr(idx, USER_FORM)) != NULL && p == idx) {
				if (tmp2->nusers >= allocated_size) {
					allocated_size += USER_ALLOC_SIZE;
					GFARM_REALLOC_ARRAY(user_tmp,
						tmp2->user_infos, allocated_size);
					if (user_tmp == NULL) {
						fprintf(stderr,
							"Error:In voms membership file('%s'[line %d])\n",
							vomsfile, line);
						e = GFARM_ERR_NO_MEMORY;
						goto err;
					}
					tmp2->user_infos = user_tmp;
					user_tmp = NULL;
				}
				p += strlen(USER_FORM);
				tmp2->user_infos[tmp2->nusers] = get_user_info_by_dn(p);
				if (tmp2->user_infos[tmp2->nusers] == NULL) {
					continue;
				}
				for (i = 0; i < tmp2->nusers; i++) {
					if (tmp2->user_infos[i] == tmp2->user_infos[tmp2->nusers]) {
						fprintf(stderr,
							"Error:In voms membership file('%s'[line %d])\n",
							vomsfile, line);
						fprintf(stderr,
							"Error:Duplicated user(%s) in same group.\n",
							tmp2->user_infos[tmp2->nusers]->username);
						goto err;
					}
				}
				tmp2->nusers++;
			}
			else {
				fprintf(stderr,
					"Error:In voms membership file('%s'[line %d])\n",
					vomsfile, line);
				fprintf(stderr, "Unknown format line:'%s'\n", idx);
				goto err;
			}
		}
	}
	if (isHead != 0) {
		fprintf(stderr, "Error: No Header\n");
		goto err;
	}
	*voms_list = tmp2->next;
	return (e);
err:
	free_group_info_list(tmp1->next);
	if (e == GFARM_ERR_NO_ERROR) {
		e = -1;
	}
	return (e);
}

/* apply changes to gfarm */
static gfarm_error_t
do_update(int op, struct group_info_list *list)
{
	gfarm_error_t e = GFARM_ERR_NO_ERROR;
	struct group_info_list *tmp = NULL;
	struct gfarm_group_info ret;
	int i;

	if (list == NULL) {
		return(e);
	}

	ret.usernames = NULL;
	tmp = list->next;
	switch(op) {
	case OP_ADD:
		while(tmp != list) {
			if (tmp->nusers == 0) {
				tmp = tmp->next;
				continue;
			}
			ret.groupname = tmp->group_name;
			ret.nusers = tmp->nusers;
			GFARM_CALLOC_ARRAY(ret.usernames, ret.nusers);
			if (ret.usernames == NULL) {
				e = GFARM_ERR_NO_MEMORY;
				goto err;
			}
			for (i = 0; i < ret.nusers; i++) {
				ret.usernames[i] = tmp->user_infos[i]->username;
			}
			e = gfm_client_group_info_set(gfm_server, &ret);
			if (e != GFARM_ERR_NO_ERROR) {
				fprintf(stderr, "Error: Failed to add group.\n");
				if (e == GFARM_ERR_ALREADY_EXISTS) {
					fprintf(stderr,
						"Group:'%s' cannot be added, ", ret.groupname);
					fprintf(stderr, "because it already exists.\n");
					fprintf(stderr,
						"The group might be updated before your operation.\n");
					fprintf(stderr, "You should try sync once more.\n");
				}
				else if (e == GFARM_ERR_NO_SUCH_USER) {
					fprintf(stderr,
						"Unknown user was tried to add.\n");
					fprintf(stderr,
						"The user might be deleted before your operation.\n");
					fprintf(stderr,
						"You should try sync once more.\n");
				}
				goto err;
			}
			free(ret.usernames);
			ret.usernames = NULL;
			tmp = tmp->next;
		}
		break;
	case OP_DEL:
		while(tmp != list) {
			e = gfm_client_group_info_remove(gfm_server, tmp->group_name);
			if (e != GFARM_ERR_NO_ERROR) {
				fprintf(stderr, "Error: Failed to delete group.\n");
				if (e == GFARM_ERR_NO_SUCH_OBJECT ||
					  e == GFARM_ERR_NO_SUCH_GROUP) {
					fprintf(stderr,
						"Group:'%s' didn't exist.\n", tmp->group_name);
					fprintf(stderr,
						"The group might be deleted before your operation.\n");
					fprintf(stderr, "You should try sync once more.\n");
				}
				goto err;
			}
			tmp = tmp->next;
		}
		break;
	case OP_MOD:
		while(tmp != list) {
			if (tmp->nusers == 0) {
				tmp = tmp->next;
				continue;
			}
			ret.groupname = tmp->group_name;
			ret.nusers = tmp->nusers;
			GFARM_CALLOC_ARRAY(ret.usernames, ret.nusers);
			if (ret.usernames == NULL) {
				e = GFARM_ERR_NO_MEMORY;
				goto err;
			}
			for (i = 0; i < ret.nusers; i++) {
				ret.usernames[i] = tmp->user_infos[i]->username;
			}
			e = gfm_client_group_info_modify(gfm_server, &ret);
			if (e != GFARM_ERR_NO_ERROR) {
				fprintf(stderr, "Error: Failed to modify group.\n");
				if (e == GFARM_ERR_NO_SUCH_OBJECT ||
					  e == GFARM_ERR_NO_SUCH_GROUP) {
					fprintf(stderr,
						"Group:'%s' didn't exist.\n", tmp->group_name);
					fprintf(stderr,
						"The group might be deleted before your operation.\n");
					fprintf(stderr, "You should try sync once more.\n");
				}
				else if (e == GFARM_ERR_NO_SUCH_USER) {
					fprintf(stderr, "Unknown user was tried to add.\n");
					fprintf(stderr,
						"The user might be deleted before your operation.\n");
					fprintf(stderr, "You should try sync once more.\n");
				}
				goto err;
			}
			free(ret.usernames);
			ret.usernames = NULL;
			tmp = tmp->next;
		}
		break;
	default:
		fprintf(stderr, "Unknown Operation for update\n");
		e = -1;
		goto err;
	}
	if (ret.usernames) free(ret.usernames);
	return (e);
err:
	if (ret.usernames) free(ret.usernames);
	return (e);
}

/* Use for debug: print out the list */
static void
print_group_list(struct group_info_list *list)
{
	int i;
	struct group_info_list *tmp = NULL;

	if (list == NULL) {
		return;
	}

	tmp = list->next;
	while(tmp != list) {
		if (tmp->nusers == 0) {
			tmp = tmp->next;
			continue;
		}
		fprintf(stdout,
			"\tgroup=%s\n", tmp->group_name);
		for (i = 0; i < tmp->nusers; i++) {
			fprintf(stdout,
				"\t\tuser=%s | gsi_dn=%s\n",
					tmp->user_infos[i]->username, tmp->user_infos[i]->gsi_dn);
		}
		tmp = tmp->next;
	}
}

/* Check whether members are same or not */
static int
isSameMembers(struct group_info_list *g, struct group_info_list *v)
{
	struct gfarm_user_info **g_members = g->user_infos;
	int g_nusers = g->nusers;
	struct gfarm_user_info **v_members = v->user_infos;
	int v_nusers = v->nusers;
	int i, j, is_same = 1, found = 0;

	if (g_nusers != v_nusers) {
		is_same = 0;
	}
	else {
		for (i = 0; i < g_nusers; i++) {
			found = 0;
			for (j = 0; j < v_nusers; j++) {
				if (g_members[i] == v_members[j]) {
					found = 1;
					break;
				}
			}
			if (found == 0) {
				is_same = 0;
				goto end;
			}
		}
	}
end:
	return is_same;
}

/* Check the deference between voms and gfarm */
static gfarm_error_t
comp_gfarm_voms(struct group_info_list *voms_list,
		struct group_info_list *gfarm_list, struct group_info_list **mod)
{
	gfarm_error_t e = GFARM_ERR_NO_ERROR;
	struct group_info_list *ret_mod = NULL;
	struct group_info_list *v_tmp = NULL;
	struct group_info_list *g_tmp = NULL;

	e = make_list_head(&ret_mod);
	if (e != GFARM_ERR_NO_ERROR) {
		goto err;
	}

	v_tmp = voms_list->next;
	while (v_tmp != voms_list) {
		g_tmp = gfarm_list->next;
		while (g_tmp != gfarm_list) {
			/* group exist */
			if (strcmp(g_tmp->group_name, v_tmp->group_name) == 0) {
				if (isSameMembers(g_tmp, v_tmp)) {
					v_tmp = remove_list(v_tmp);
					g_tmp = remove_list(g_tmp);
				}
				else {
					v_tmp = add_list(ret_mod, v_tmp);
					g_tmp = remove_list(g_tmp);
				}
				break;
			}
			else {
				g_tmp = g_tmp->next;
			}
		}
		v_tmp = v_tmp->next;
	}

	*mod = ret_mod;
	return (e);
err:
	if (ret_mod) free_group_info_list(ret_mod);
	if (e == GFARM_ERR_NO_ERROR) {
		e = -1;
	}
	return (e);
}

/* check and apply the changes */
static gfarm_error_t
execute_update(struct group_info_list *gfarm_list,  char noupd, char del)
{
	gfarm_error_t e = GFARM_ERR_NO_ERROR;
	struct group_info_list *voms_list = NULL;
	struct group_info_list *mod_group = NULL;
	FILE *vomsfp = NULL;

	if (del == 0) {
		vomsfp = fopen(vomsfile, "r");
		if (vomsfp == NULL) {
			fprintf(stderr, "Failed to open voms membership file.\n");
			e = GFARM_ERR_NO_SUCH_FILE_OR_DIRECTORY;
			goto err;
		}
		e = make_voms_membership_list(vomsfp, &voms_list);
		if (e != GFARM_ERR_NO_ERROR) {
			fprintf(stderr, "Failed to read voms membership list.\n");
			goto err;
		}

		e = comp_gfarm_voms(voms_list, gfarm_list, &mod_group);
		if (e != GFARM_ERR_NO_ERROR) {
			goto err;
		}
	}

	if (noupd) {
		fprintf(stdout, "New Groups\n");
		print_group_list(voms_list);
		fprintf(stdout, "Delete Groups\n");
		print_group_list(gfarm_list);
		fprintf(stdout, "Modify Groups\n");
		print_group_list(mod_group);
	}
	else {
		e = do_update(OP_ADD, voms_list);
		if (e != GFARM_ERR_NO_ERROR) {
			goto err;
		}
		e = do_update(OP_DEL, gfarm_list);
		if (e != GFARM_ERR_NO_ERROR) {
			goto err;
		}
		e = do_update(OP_MOD, mod_group);
		if (e != GFARM_ERR_NO_ERROR) {
			goto err;
		}
	}

	if (voms_list) free_group_info_list(voms_list);
	if (mod_group) free_group_info_list(mod_group);
	if (vomsfp) fclose(vomsfp);
	return (e);
err:
	if (voms_list) free_group_info_list(voms_list);
	if (mod_group) free_group_info_list(mod_group);
	if (vomsfp) fclose(vomsfp);
	if (e == GFARM_ERR_NO_ERROR) {
		e = -1;
	}
	return (e);
}

static int
checkVomsIDChars(void)
{
	int i;

	for (i = 0; i < strlen(vomsid); i++) {
		if (!isalnum(vomsid[i]) && vomsid[i] != '-' && vomsid[i] != '_') {
			return 0;
		}
	}
	return 1;
}

static gfarm_error_t
init(char del)
{
	gfarm_error_t e = GFARM_ERR_NO_ERROR;

	if ((!del) && vomsfile == NULL) {
		fprintf(stderr, "NO VOMS membership file path is specified!\n");
		goto err;
	}

	if (vo == NULL) {
		fprintf(stderr, "NO vo is specified!\n");
		goto err;
	}

	if (vomsid == NULL) {
		fprintf(stderr, "NO vomsid is specified!\n");
		goto err;
	}
	if (!checkVomsIDChars()) {
		fprintf(stderr,
			"The chars which can use for vomsID are [a-zA-Z0-9_-].\n");
		goto err;
	}

	e = list_groups();
	if (e != GFARM_ERR_NO_ERROR) {
		if (e != -1) {
			fprintf(stderr, "%s\n", gfarm_error_string(e));
		}
		goto err;
	}

	e = list_users();
	if (e != GFARM_ERR_NO_ERROR) {
		if (e != -1) {
			fprintf(stderr, "%s\n", gfarm_error_string(e));
		}
		goto err;
	}
	return (e);
err:
	if (e == GFARM_ERR_NO_ERROR) {
		e = -1;
	}
	return (e);
}

static void
term(void)
{
	if (vomsid) free(vomsid);
	if (vo) free(vo);
	if (vomsfile) free(vomsfile);
	gfarm_group_info_free_all(ngroups, groups);
	gfarm_user_info_free_all(nusers, users);
}

int
main(int argc, char *argv[])
{
	gfarm_error_t e = GFARM_ERR_NO_ERROR;
	struct group_info_list *gfarm_list = NULL;
	char noupd = 0, del = 0, c;
	const char *opt_path = GFARM_PATH_ROOT;

	e = gfarm_initialize(&argc, &argv);
	if (e != GFARM_ERR_NO_ERROR) {
		fprintf(stderr, "gfarm_initialize: %s\n",gfarm_error_string(e));
		goto err;
	}

	while ((c = getopt(argc, argv, ":V:v:m:hnd")) != -1) {
		switch (c) {
		case OP_VOMS_FILE:
			SET_VOMS_MEMBER_FILE(optarg, e);
			if (e != GFARM_ERR_NO_ERROR) {
				fprintf(stderr,
					"%s\n", gfarm_error_string(GFARM_ERR_NO_MEMORY));
				goto err;
			}
			break;
		case OP_VOMSID:
			SET_VOMSID(optarg, e);
			if (e != GFARM_ERR_NO_ERROR) {
				fprintf(stderr,
					"%s\n", gfarm_error_string(GFARM_ERR_NO_MEMORY));
				goto err;
			}
			break;
		case OP_VO:
			SET_VO(optarg, e);
			if (e != GFARM_ERR_NO_ERROR) {
				fprintf(stderr,
					"%s\n", gfarm_error_string(GFARM_ERR_NO_MEMORY));
				goto err;
			}
			break;
		case OP_NO_UPDATE:
			noupd = 1;
			break;
		case OP_DELETE:
			del = 1;
			break;
		case OP_USAGE:
			usage();
			goto end;
		case ':':
		case '?':
		default:
			fprintf(stderr, "Unknown option\n");
			usage();
			goto err;
		}
	}

	if ((e = gfm_client_connection_and_process_acquire_by_path(opt_path,
	    &gfm_server)) != GFARM_ERR_NO_ERROR) {
		fprintf(stderr, "%s: metadata server for \"%s\": %s\n",
		    program_name, opt_path, gfarm_error_string(e));
		goto err;
	}

	fprintf(stdout, "Start updating...\n");

	e = init(del);
	if (e != GFARM_ERR_NO_ERROR) {
		if (e != -1) {
			fprintf(stderr, "Error:%s\n", gfarm_error_string(e));
		}
		goto err;
	}

	e = make_gfarm_membership_list(&gfarm_list);
	if (e != GFARM_ERR_NO_ERROR) {
		fprintf(stderr, "Failed to read gfarm membership.\n");
		if (e != -1) {
			fprintf(stderr, "Error:%s\n", gfarm_error_string(e));
		}
		goto err;
	}

	e = execute_update(gfarm_list, noupd, del);
	if (e != GFARM_ERR_NO_ERROR) {
		if (e != -1) {
			fprintf(stderr, "Error:%s\n", gfarm_error_string(e));
		}
		goto err;
	}

	gfm_client_connection_free(gfm_server);
	e = gfarm_terminate();
	if (e != GFARM_ERR_NO_ERROR) {
		if (e != -1) {
			fprintf(stderr, "Error:%s\n", gfarm_error_string(e));
		}
		goto err;
	}

	fprintf(stdout, "Finished\n");

end:
	if (gfarm_list) free_group_info_list(gfarm_list);
	term();
	exit(0);
err:
	if (gfarm_list) free_group_info_list(gfarm_list);
	term();
	exit(1);
}
