summaryrefslogtreecommitdiff
path: root/utils/open-isns/local.c
blob: 4bc1cb1f2a2dc83b05b2a4e0e8af5c993bd429ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
/*
 * Local iSNS registration
 *
 * Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com>
 *
 * The way isnsdd communicates with local services (initiator,
 * target) is via a file and signals. That sounds rather
 * awkward, but it's a lot simpler to add to these services
 * than another socket based communication mechanism I guess.
 *
 * The file format is simple:
 *  <object> owner=<owner>
 *  <object> owner=<owner>
 *  ...
 *
 * <owner> identifies the service owning these entries.
 * This is a service name, such as iscsid, tgtd, isnsdd,
 * optionally followed by a colon and a PID. This allows
 * removal of all entries created by one service in one go.
 *
 * <object> is the description of one iSNS object, using the
 * syntax used by all other open-isns apps.
 */

#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>

#include <isns.h>
#include "security.h"
#include "util.h"
#include "isns-proto.h"
#include "paths.h"
#include "attrs.h"
#include "util.h"

typedef int __isns_local_registry_cb_fn_t(const char *line,
			int argc, char **argv,
			void *user_data);

/*
 * Build the owner=<svcname>:<pid> tag
 */
static const char *
__isns_local_registry_make_owner(const char *svcname, pid_t pid)
{
	static char owner[128];

	if (pid == 0) {
		return svcname;
	}
	snprintf(owner, sizeof(owner), "%s:%u", svcname, pid);
	return owner;
}

/*
 * Read the registry file, match each entry against the given owner=<foo> tag,
 * and invoke the callback function.
 * This is used for both reading the registry, and rewriting it.
 */
static int
__isns_local_registry_read(const char *match_owner,
			__isns_local_registry_cb_fn_t handle_matching,
			__isns_local_registry_cb_fn_t handle_nonmatching,
			void *user_data)
{
	const char	*filename = isns_config.ic_local_registry_file;
	char		*line, *copy = NULL;
	FILE		*fp;
	int		rv = 0, owner_len;

	if (!(fp = fopen(filename, "r"))) {
		if (errno == ENOENT) {
			isns_debug_state("Unable to open %s: %m\n", filename);
			return 1;
		}
		isns_error("Unable to open %s: %m\n", filename);
		return 0;
	}

	owner_len = match_owner? strlen(match_owner) : 0;
	while ((line = parser_get_next_line(fp)) != NULL) {
		__isns_local_registry_cb_fn_t *cb;
		char	*argv[256], *owner;
		int	argc = 0;

		isns_assign_string(&copy, line);

		argc = isns_attr_list_split(line, argv, 255);
		if (argc <= 0)
			continue;

		/* Last attr should be owner */
		if (strncasecmp(argv[argc-1], "owner=", 6)) {
			isns_error("%s: syntax error (missing owner field)\n",
					filename);
			goto out;
		}
		owner = argv[argc-1] + 6;

		if (!strncasecmp(owner, match_owner, owner_len)
		 && (owner[owner_len] == '\0' || owner[owner_len] == ':'))
			cb = handle_matching;
		else
			cb = handle_nonmatching;

		if (cb && !cb(copy, argc, argv, user_data))
			goto out;

	}
	rv = 1;

out:
	free(copy);
	fclose(fp);
	return rv;
}

/*
 * Open and lock the registry file for writing. Returns an
 * open stream and the name of the lock file.
 * Follow up with _finish_write when done.
 */
static FILE *
__isns_local_registry_open_write(char **lock_name)
{
	char	lock_path[PATH_MAX];
	FILE	*fp;
	int	fd, retry;

	snprintf(lock_path, sizeof(lock_path), "%s.lock",
			isns_config.ic_local_registry_file);

	for (retry = 0; retry < 5; ++retry) {
		fd = open(lock_path, O_RDWR|O_CREAT|O_EXCL, 0644);
		if (fd >= 0)
			break;
		if (errno != EEXIST) {
			isns_error("Unable to create %s: %m\n",
					lock_path);
			return NULL;
		}
		isns_error("Cannot lock %s - retry in 1 sec\n",
					isns_config.ic_local_registry_file);
		sleep(1);
	}

	if (!(fp = fdopen(fd, "w"))) {
		isns_error("fdopen failed: %m\n");
		close(fd);
		return NULL;
	}
	isns_assign_string(lock_name, lock_path);
	return fp;
}

/*
 * We're done with (re)writing the registry. Commit the changes,
 * or discard them.
 * Also frees the lock_name returned by registry_open_write.
 */
static int
__isns_local_registry_finish_write(FILE *fp, char *lock_name, int commit)
{
	int	rv = 1;

	fclose(fp);
	if (!commit) {
		if (unlink(lock_name))
			isns_error("Failed to unlink %s: %m\n", lock_name);
	} else
	if (rename(lock_name, isns_config.ic_local_registry_file)) {
		isns_error("Failed to rename %s to %s: %m\n",
				lock_name, isns_config.ic_local_registry_file);
		rv = 0;
	}

	free(lock_name);
	return rv;
}

/*
 * Get the entity name for this service
 */
static char *
__isns_local_registry_entity_name(const char *owner)
{
	static char	namebuf[1024];

	snprintf(namebuf, sizeof(namebuf), "%s:%s",
			isns_config.ic_entity_name,
			owner);
	return namebuf;
}

/*
 * Callback function which builds an iSNS object from the
 * list of attr=tag values.
 */
static int
__isns_local_registry_load_object(const char *line,
		int argc, char **argv, void *user_data)
{
	isns_attr_list_t attrs = ISNS_ATTR_LIST_INIT;
	struct isns_attr_list_parser state;
	isns_object_list_t *list = user_data;
	isns_object_t *obj, *entity = NULL;

	for (; argc > 0; --argc) {
		char	*attr = argv[argc-1];

		if (!strncasecmp(attr, "owner=", 6)) {
			char *eid = __isns_local_registry_entity_name(attr + 6);
			ISNS_QUICK_ATTR_LIST_DECLARE(key_attrs,
					ISNS_TAG_ENTITY_IDENTIFIER,
					string, eid);

			if (entity) {
				isns_error("Duplicate owner entry in registry\n");
				continue;
			}
			isns_attr_print(&key_attrs.iqa_attr, isns_print_stdout);
			entity = isns_object_list_lookup(list,
					&isns_entity_template,
					&key_attrs.iqa_list);
			if (entity != NULL)
				continue;

			isns_debug_state("Creating fake entity %s\n", eid);
			entity = isns_create_entity(ISNS_ENTITY_PROTOCOL_ISCSI, eid);
			isns_object_list_append(list, entity);
		} else {
			break;
		}
	}

	isns_attr_list_parser_init(&state, NULL);
	if (!isns_parse_attrs(argc, argv, &attrs, &state)) {
		isns_error("Unable to parse attrs\n");
		isns_attr_list_destroy(&attrs);
		return 0;
	}

	obj = isns_create_object(isns_attr_list_parser_context(&state),
					&attrs, entity);
	isns_attr_list_destroy(&attrs);

	if (obj == NULL) {
		isns_error("Unable to create object\n");
		return 0;
	}

	isns_object_list_append(list, obj);
	return 1;
}

/*
 * Callback function that simply writes out the line as-is
 */
static int
__isns_local_registry_rewrite_object(const char *line,
		int argc, char **argv, void *user_data)
{
	FILE	*ofp = user_data;

	fprintf(ofp, "%s\n", line);
	return 1;
}

/*
 * Load all objects owner by a specific service from the local registry.
 * If the svcname starts with "!", all entries except those matching this
 * particular service are returned.
 */
int
isns_local_registry_load(const char *svcname, pid_t pid, isns_object_list_t *objs)
{
	__isns_local_registry_cb_fn_t *if_matching = NULL, *if_nonmatching = NULL;

	if (svcname == NULL) {
		isns_error("%s: no svcname given\n", __FUNCTION__);
		return 0;
	}
	if (*svcname == '!') {
		if_nonmatching = __isns_local_registry_load_object;
		svcname++;
	} else {
		if_matching = __isns_local_registry_load_object;
	}

	return __isns_local_registry_read(
			__isns_local_registry_make_owner(svcname, pid),
			if_matching, if_nonmatching, objs);
}

/*
 * Store the given list of objects in the registry.
 * This replaces all objects previously registered by this service.
 */
int
isns_local_registry_store(const char *svcname, pid_t pid, const isns_object_list_t *objs)
{
	const char *owner = __isns_local_registry_make_owner(svcname, pid);
	char	*lock_name = NULL;
	FILE	*ofp;

	if (!(ofp = __isns_local_registry_open_write(&lock_name))) {
		isns_error("%s: could not open registry for writing\n", __FUNCTION__);
		return 0;
	}

	/* First, purge all entries previously belonging to this owner */
	if (!__isns_local_registry_read(owner, NULL, __isns_local_registry_rewrite_object, ofp))
		goto failed;

	if (objs) {
		unsigned int	i;

		for (i = 0; i < objs->iol_count; ++i) {
			isns_object_t *obj = objs->iol_data[i];
			char *argv[256];
			int i, argc;

			argc = isns_print_attrs(obj, argv, 256);
			for (i = 0; i < argc; ++i)
				fprintf(ofp, "%s ", argv[i]);
			fprintf(ofp, "owner=%s\n", owner);
		}
	}

	return __isns_local_registry_finish_write(ofp, lock_name, 1);

failed:
	isns_error("%s: error rewriting registry file\n", __FUNCTION__);
	__isns_local_registry_finish_write(ofp, lock_name, 0);
	return 0;
}

/*
 * Purge the local registry of all objects owned by the
 * given service.
 */
int
isns_local_registry_purge(const char *svcname, pid_t pid)
{
	return isns_local_registry_store(svcname, pid, NULL);
}