test_maps.c 16.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * Testsuite for eBPF maps
 *
 * Copyright (c) 2014 PLUMgrid, http://plumgrid.com
 * Copyright (c) 2016 Facebook
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 */

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>

#include <sys/wait.h>
#include <sys/resource.h>

#include <linux/bpf.h>

24
#include <bpf/bpf.h>
25
#include "bpf_util.h"
26 27 28 29 30

static int map_flags;

static void test_hashmap(int task, void *data)
{
31
	long long key, next_key, first_key, value;
32 33
	int fd;

34
	fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(key), sizeof(value),
35 36 37 38 39 40 41 42 43
			    2, map_flags);
	if (fd < 0) {
		printf("Failed to create hashmap '%s'!\n", strerror(errno));
		exit(1);
	}

	key = 1;
	value = 1234;
	/* Insert key=1 element. */
44
	assert(bpf_map_update_elem(fd, &key, &value, BPF_ANY) == 0);
45 46 47

	value = 0;
	/* BPF_NOEXIST means add new element if it doesn't exist. */
48
	assert(bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST) == -1 &&
49 50 51 52
	       /* key=1 already exists. */
	       errno == EEXIST);

	/* -1 is an invalid flag. */
53 54
	assert(bpf_map_update_elem(fd, &key, &value, -1) == -1 &&
	       errno == EINVAL);
55 56

	/* Check that key=1 can be found. */
57
	assert(bpf_map_lookup_elem(fd, &key, &value) == 0 && value == 1234);
58 59 60

	key = 2;
	/* Check that key=2 is not found. */
61
	assert(bpf_map_lookup_elem(fd, &key, &value) == -1 && errno == ENOENT);
62 63

	/* BPF_EXIST means update existing element. */
64
	assert(bpf_map_update_elem(fd, &key, &value, BPF_EXIST) == -1 &&
65 66 67 68
	       /* key=2 is not there. */
	       errno == ENOENT);

	/* Insert key=2 element. */
69
	assert(bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST) == 0);
70 71 72 73 74

	/* key=1 and key=2 were inserted, check that key=0 cannot be
	 * inserted due to max_entries limit.
	 */
	key = 0;
75
	assert(bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST) == -1 &&
76 77 78 79
	       errno == E2BIG);

	/* Update existing element, though the map is full. */
	key = 1;
80
	assert(bpf_map_update_elem(fd, &key, &value, BPF_EXIST) == 0);
81
	key = 2;
82
	assert(bpf_map_update_elem(fd, &key, &value, BPF_ANY) == 0);
83 84 85
	key = 3;
	assert(bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST) == -1 &&
	       errno == E2BIG);
86 87 88

	/* Check that key = 0 doesn't exist. */
	key = 0;
89
	assert(bpf_map_delete_elem(fd, &key) == -1 && errno == ENOENT);
90 91

	/* Iterate over two elements. */
92 93
	assert(bpf_map_get_next_key(fd, NULL, &first_key) == 0 &&
	       (first_key == 1 || first_key == 2));
94
	assert(bpf_map_get_next_key(fd, &key, &next_key) == 0 &&
95
	       (next_key == first_key));
96
	assert(bpf_map_get_next_key(fd, &next_key, &next_key) == 0 &&
97 98
	       (next_key == 1 || next_key == 2) &&
	       (next_key != first_key));
99
	assert(bpf_map_get_next_key(fd, &next_key, &next_key) == -1 &&
100 101 102 103
	       errno == ENOENT);

	/* Delete both elements. */
	key = 1;
104
	assert(bpf_map_delete_elem(fd, &key) == 0);
105
	key = 2;
106 107
	assert(bpf_map_delete_elem(fd, &key) == 0);
	assert(bpf_map_delete_elem(fd, &key) == -1 && errno == ENOENT);
108 109 110

	key = 0;
	/* Check that map is empty. */
111 112
	assert(bpf_map_get_next_key(fd, NULL, &next_key) == -1 &&
	       errno == ENOENT);
113
	assert(bpf_map_get_next_key(fd, &key, &next_key) == -1 &&
114 115 116 117 118
	       errno == ENOENT);

	close(fd);
}

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
static void test_hashmap_sizes(int task, void *data)
{
	int fd, i, j;

	for (i = 1; i <= 512; i <<= 1)
		for (j = 1; j <= 1 << 18; j <<= 1) {
			fd = bpf_create_map(BPF_MAP_TYPE_HASH, i, j,
					    2, map_flags);
			if (fd < 0) {
				printf("Failed to create hashmap key=%d value=%d '%s'\n",
				       i, j, strerror(errno));
				exit(1);
			}
			close(fd);
			usleep(10); /* give kernel time to destroy */
		}
}

137 138
static void test_hashmap_percpu(int task, void *data)
{
139
	unsigned int nr_cpus = bpf_num_possible_cpus();
140
	BPF_DECLARE_PERCPU(long, value);
141
	long long key, next_key, first_key;
142 143 144
	int expected_key_mask = 0;
	int fd, i;

145
	fd = bpf_create_map(BPF_MAP_TYPE_PERCPU_HASH, sizeof(key),
146
			    sizeof(bpf_percpu(value, 0)), 2, map_flags);
147 148 149 150 151 152
	if (fd < 0) {
		printf("Failed to create hashmap '%s'!\n", strerror(errno));
		exit(1);
	}

	for (i = 0; i < nr_cpus; i++)
153
		bpf_percpu(value, i) = i + 100;
154 155 156 157

	key = 1;
	/* Insert key=1 element. */
	assert(!(expected_key_mask & key));
158
	assert(bpf_map_update_elem(fd, &key, value, BPF_ANY) == 0);
159 160 161
	expected_key_mask |= key;

	/* BPF_NOEXIST means add new element if it doesn't exist. */
162
	assert(bpf_map_update_elem(fd, &key, value, BPF_NOEXIST) == -1 &&
163 164 165 166
	       /* key=1 already exists. */
	       errno == EEXIST);

	/* -1 is an invalid flag. */
167 168
	assert(bpf_map_update_elem(fd, &key, value, -1) == -1 &&
	       errno == EINVAL);
169 170 171 172

	/* Check that key=1 can be found. Value could be 0 if the lookup
	 * was run from a different CPU.
	 */
173 174 175
	bpf_percpu(value, 0) = 1;
	assert(bpf_map_lookup_elem(fd, &key, value) == 0 &&
	       bpf_percpu(value, 0) == 100);
176 177 178

	key = 2;
	/* Check that key=2 is not found. */
179
	assert(bpf_map_lookup_elem(fd, &key, value) == -1 && errno == ENOENT);
180 181

	/* BPF_EXIST means update existing element. */
182
	assert(bpf_map_update_elem(fd, &key, value, BPF_EXIST) == -1 &&
183 184 185 186 187
	       /* key=2 is not there. */
	       errno == ENOENT);

	/* Insert key=2 element. */
	assert(!(expected_key_mask & key));
188
	assert(bpf_map_update_elem(fd, &key, value, BPF_NOEXIST) == 0);
189 190 191 192 193 194
	expected_key_mask |= key;

	/* key=1 and key=2 were inserted, check that key=0 cannot be
	 * inserted due to max_entries limit.
	 */
	key = 0;
195
	assert(bpf_map_update_elem(fd, &key, value, BPF_NOEXIST) == -1 &&
196 197 198
	       errno == E2BIG);

	/* Check that key = 0 doesn't exist. */
199
	assert(bpf_map_delete_elem(fd, &key) == -1 && errno == ENOENT);
200 201

	/* Iterate over two elements. */
202 203
	assert(bpf_map_get_next_key(fd, NULL, &first_key) == 0 &&
	       ((expected_key_mask & first_key) == first_key));
204
	while (!bpf_map_get_next_key(fd, &key, &next_key)) {
205 206 207 208
		if (first_key) {
			assert(next_key == first_key);
			first_key = 0;
		}
209 210 211
		assert((expected_key_mask & next_key) == next_key);
		expected_key_mask &= ~next_key;

212
		assert(bpf_map_lookup_elem(fd, &next_key, value) == 0);
213 214

		for (i = 0; i < nr_cpus; i++)
215
			assert(bpf_percpu(value, i) == i + 100);
216 217 218 219 220 221 222

		key = next_key;
	}
	assert(errno == ENOENT);

	/* Update with BPF_EXIST. */
	key = 1;
223
	assert(bpf_map_update_elem(fd, &key, value, BPF_EXIST) == 0);
224 225 226

	/* Delete both elements. */
	key = 1;
227
	assert(bpf_map_delete_elem(fd, &key) == 0);
228
	key = 2;
229 230
	assert(bpf_map_delete_elem(fd, &key) == 0);
	assert(bpf_map_delete_elem(fd, &key) == -1 && errno == ENOENT);
231 232 233

	key = 0;
	/* Check that map is empty. */
234 235
	assert(bpf_map_get_next_key(fd, NULL, &next_key) == -1 &&
	       errno == ENOENT);
236
	assert(bpf_map_get_next_key(fd, &key, &next_key) == -1 &&
237 238 239 240 241
	       errno == ENOENT);

	close(fd);
}

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
static void test_hashmap_walk(int task, void *data)
{
	int fd, i, max_entries = 100000;
	long long key, value, next_key;
	bool next_key_valid = true;

	fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(key), sizeof(value),
			    max_entries, map_flags);
	if (fd < 0) {
		printf("Failed to create hashmap '%s'!\n", strerror(errno));
		exit(1);
	}

	for (i = 0; i < max_entries; i++) {
		key = i; value = key;
		assert(bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST) == 0);
	}

	for (i = 0; bpf_map_get_next_key(fd, !i ? NULL : &key,
					 &next_key) == 0; i++) {
		key = next_key;
		assert(bpf_map_lookup_elem(fd, &key, &value) == 0);
	}

	assert(i == max_entries);

	assert(bpf_map_get_next_key(fd, NULL, &key) == 0);
	for (i = 0; next_key_valid; i++) {
		next_key_valid = bpf_map_get_next_key(fd, &key, &next_key) == 0;
		assert(bpf_map_lookup_elem(fd, &key, &value) == 0);
		value++;
		assert(bpf_map_update_elem(fd, &key, &value, BPF_EXIST) == 0);
		key = next_key;
	}

	assert(i == max_entries);

	for (i = 0; bpf_map_get_next_key(fd, !i ? NULL : &key,
					 &next_key) == 0; i++) {
		key = next_key;
		assert(bpf_map_lookup_elem(fd, &key, &value) == 0);
		assert(value - 1 == key);
	}

	assert(i == max_entries);
	close(fd);
}

290 291 292 293 294
static void test_arraymap(int task, void *data)
{
	int key, next_key, fd;
	long long value;

295
	fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value),
296 297 298 299 300 301 302 303 304
			    2, 0);
	if (fd < 0) {
		printf("Failed to create arraymap '%s'!\n", strerror(errno));
		exit(1);
	}

	key = 1;
	value = 1234;
	/* Insert key=1 element. */
305
	assert(bpf_map_update_elem(fd, &key, &value, BPF_ANY) == 0);
306 307

	value = 0;
308
	assert(bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST) == -1 &&
309 310 311
	       errno == EEXIST);

	/* Check that key=1 can be found. */
312
	assert(bpf_map_lookup_elem(fd, &key, &value) == 0 && value == 1234);
313 314 315

	key = 0;
	/* Check that key=0 is also found and zero initialized. */
316
	assert(bpf_map_lookup_elem(fd, &key, &value) == 0 && value == 0);
317 318 319 320 321

	/* key=0 and key=1 were inserted, check that key=2 cannot be inserted
	 * due to max_entries limit.
	 */
	key = 2;
322
	assert(bpf_map_update_elem(fd, &key, &value, BPF_EXIST) == -1 &&
323 324 325
	       errno == E2BIG);

	/* Check that key = 2 doesn't exist. */
326
	assert(bpf_map_lookup_elem(fd, &key, &value) == -1 && errno == ENOENT);
327 328

	/* Iterate over two elements. */
329 330
	assert(bpf_map_get_next_key(fd, NULL, &next_key) == 0 &&
	       next_key == 0);
331
	assert(bpf_map_get_next_key(fd, &key, &next_key) == 0 &&
332
	       next_key == 0);
333
	assert(bpf_map_get_next_key(fd, &next_key, &next_key) == 0 &&
334
	       next_key == 1);
335
	assert(bpf_map_get_next_key(fd, &next_key, &next_key) == -1 &&
336 337 338 339
	       errno == ENOENT);

	/* Delete shouldn't succeed. */
	key = 1;
340
	assert(bpf_map_delete_elem(fd, &key) == -1 && errno == EINVAL);
341 342 343 344 345 346

	close(fd);
}

static void test_arraymap_percpu(int task, void *data)
{
347
	unsigned int nr_cpus = bpf_num_possible_cpus();
348
	BPF_DECLARE_PERCPU(long, values);
349 350
	int key, next_key, fd, i;

351
	fd = bpf_create_map(BPF_MAP_TYPE_PERCPU_ARRAY, sizeof(key),
352
			    sizeof(bpf_percpu(values, 0)), 2, 0);
353 354 355 356 357 358
	if (fd < 0) {
		printf("Failed to create arraymap '%s'!\n", strerror(errno));
		exit(1);
	}

	for (i = 0; i < nr_cpus; i++)
359
		bpf_percpu(values, i) = i + 100;
360 361 362

	key = 1;
	/* Insert key=1 element. */
363
	assert(bpf_map_update_elem(fd, &key, values, BPF_ANY) == 0);
364

365
	bpf_percpu(values, 0) = 0;
366
	assert(bpf_map_update_elem(fd, &key, values, BPF_NOEXIST) == -1 &&
367 368 369
	       errno == EEXIST);

	/* Check that key=1 can be found. */
370 371
	assert(bpf_map_lookup_elem(fd, &key, values) == 0 &&
	       bpf_percpu(values, 0) == 100);
372 373 374

	key = 0;
	/* Check that key=0 is also found and zero initialized. */
375
	assert(bpf_map_lookup_elem(fd, &key, values) == 0 &&
376 377
	       bpf_percpu(values, 0) == 0 &&
	       bpf_percpu(values, nr_cpus - 1) == 0);
378 379 380

	/* Check that key=2 cannot be inserted due to max_entries limit. */
	key = 2;
381
	assert(bpf_map_update_elem(fd, &key, values, BPF_EXIST) == -1 &&
382 383 384
	       errno == E2BIG);

	/* Check that key = 2 doesn't exist. */
385
	assert(bpf_map_lookup_elem(fd, &key, values) == -1 && errno == ENOENT);
386 387

	/* Iterate over two elements. */
388 389
	assert(bpf_map_get_next_key(fd, NULL, &next_key) == 0 &&
	       next_key == 0);
390
	assert(bpf_map_get_next_key(fd, &key, &next_key) == 0 &&
391
	       next_key == 0);
392
	assert(bpf_map_get_next_key(fd, &next_key, &next_key) == 0 &&
393
	       next_key == 1);
394
	assert(bpf_map_get_next_key(fd, &next_key, &next_key) == -1 &&
395 396 397 398
	       errno == ENOENT);

	/* Delete shouldn't succeed. */
	key = 1;
399
	assert(bpf_map_delete_elem(fd, &key) == -1 && errno == EINVAL);
400 401 402 403 404 405

	close(fd);
}

static void test_arraymap_percpu_many_keys(void)
{
406
	unsigned int nr_cpus = bpf_num_possible_cpus();
407
	BPF_DECLARE_PERCPU(long, values);
408 409 410 411
	/* nr_keys is not too large otherwise the test stresses percpu
	 * allocator more than anything else
	 */
	unsigned int nr_keys = 2000;
412 413
	int key, fd, i;

414
	fd = bpf_create_map(BPF_MAP_TYPE_PERCPU_ARRAY, sizeof(key),
415
			    sizeof(bpf_percpu(values, 0)), nr_keys, 0);
416 417 418 419 420 421 422
	if (fd < 0) {
		printf("Failed to create per-cpu arraymap '%s'!\n",
		       strerror(errno));
		exit(1);
	}

	for (i = 0; i < nr_cpus; i++)
423
		bpf_percpu(values, i) = i + 10;
424 425

	for (key = 0; key < nr_keys; key++)
426
		assert(bpf_map_update_elem(fd, &key, values, BPF_ANY) == 0);
427 428 429

	for (key = 0; key < nr_keys; key++) {
		for (i = 0; i < nr_cpus; i++)
430
			bpf_percpu(values, i) = 0;
431

432
		assert(bpf_map_lookup_elem(fd, &key, values) == 0);
433 434

		for (i = 0; i < nr_cpus; i++)
435
			assert(bpf_percpu(values, i) == i + 10);
436 437 438 439 440
	}

	close(fd);
}

441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
static void test_devmap(int task, void *data)
{
	int next_key, fd;
	__u32 key, value;

	fd = bpf_create_map(BPF_MAP_TYPE_DEVMAP, sizeof(key), sizeof(value),
			    2, 0);
	if (fd < 0) {
		printf("Failed to create arraymap '%s'!\n", strerror(errno));
		exit(1);
	}

	close(fd);
}

456 457 458 459 460 461 462 463 464 465 466
#define MAP_SIZE (32 * 1024)

static void test_map_large(void)
{
	struct bigkey {
		int a;
		char b[116];
		long long c;
	} key;
	int fd, i, value;

467
	fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(key), sizeof(value),
468 469 470 471 472 473 474 475 476 477
			    MAP_SIZE, map_flags);
	if (fd < 0) {
		printf("Failed to create large map '%s'!\n", strerror(errno));
		exit(1);
	}

	for (i = 0; i < MAP_SIZE; i++) {
		key = (struct bigkey) { .c = i };
		value = i;

478
		assert(bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST) == 0);
479 480 481
	}

	key.c = -1;
482
	assert(bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST) == -1 &&
483 484 485
	       errno == E2BIG);

	/* Iterate through all elements. */
486 487
	assert(bpf_map_get_next_key(fd, NULL, &key) == 0);
	key.c = -1;
488
	for (i = 0; i < MAP_SIZE; i++)
489 490
		assert(bpf_map_get_next_key(fd, &key, &key) == 0);
	assert(bpf_map_get_next_key(fd, &key, &key) == -1 && errno == ENOENT);
491 492

	key.c = 0;
493
	assert(bpf_map_lookup_elem(fd, &key, &value) == 0 && value == 0);
494
	key.a = 1;
495
	assert(bpf_map_lookup_elem(fd, &key, &value) == -1 && errno == ENOENT);
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528

	close(fd);
}

static void run_parallel(int tasks, void (*fn)(int task, void *data),
			 void *data)
{
	pid_t pid[tasks];
	int i;

	for (i = 0; i < tasks; i++) {
		pid[i] = fork();
		if (pid[i] == 0) {
			fn(i, data);
			exit(0);
		} else if (pid[i] == -1) {
			printf("Couldn't spawn #%d process!\n", i);
			exit(1);
		}
	}

	for (i = 0; i < tasks; i++) {
		int status;

		assert(waitpid(pid[i], &status, 0) == pid[i]);
		assert(status == 0);
	}
}

static void test_map_stress(void)
{
	run_parallel(100, test_hashmap, NULL);
	run_parallel(100, test_hashmap_percpu, NULL);
529
	run_parallel(100, test_hashmap_sizes, NULL);
530
	run_parallel(100, test_hashmap_walk, NULL);
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550

	run_parallel(100, test_arraymap, NULL);
	run_parallel(100, test_arraymap_percpu, NULL);
}

#define TASKS 1024

#define DO_UPDATE 1
#define DO_DELETE 0

static void do_work(int fn, void *data)
{
	int do_update = ((int *)data)[1];
	int fd = ((int *)data)[0];
	int i, key, value;

	for (i = fn; i < MAP_SIZE; i += TASKS) {
		key = value = i;

		if (do_update) {
551 552 553 554
			assert(bpf_map_update_elem(fd, &key, &value,
						   BPF_NOEXIST) == 0);
			assert(bpf_map_update_elem(fd, &key, &value,
						   BPF_EXIST) == 0);
555
		} else {
556
			assert(bpf_map_delete_elem(fd, &key) == 0);
557 558 559 560 561 562 563 564 565
		}
	}
}

static void test_map_parallel(void)
{
	int i, fd, key = 0, value = 0;
	int data[2];

566
	fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(key), sizeof(value),
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
			    MAP_SIZE, map_flags);
	if (fd < 0) {
		printf("Failed to create map for parallel test '%s'!\n",
		       strerror(errno));
		exit(1);
	}

	/* Use the same fd in children to add elements to this map:
	 * child_0 adds key=0, key=1024, key=2048, ...
	 * child_1 adds key=1, key=1025, key=2049, ...
	 * child_1023 adds key=1023, ...
	 */
	data[0] = fd;
	data[1] = DO_UPDATE;
	run_parallel(TASKS, do_work, data);

	/* Check that key=0 is already there. */
584
	assert(bpf_map_update_elem(fd, &key, &value, BPF_NOEXIST) == -1 &&
585 586 587
	       errno == EEXIST);

	/* Check that all elements were inserted. */
588
	assert(bpf_map_get_next_key(fd, NULL, &key) == 0);
589 590
	key = -1;
	for (i = 0; i < MAP_SIZE; i++)
591 592
		assert(bpf_map_get_next_key(fd, &key, &key) == 0);
	assert(bpf_map_get_next_key(fd, &key, &key) == -1 && errno == ENOENT);
593 594 595 596 597

	/* Another check for all elements */
	for (i = 0; i < MAP_SIZE; i++) {
		key = MAP_SIZE - i - 1;

598
		assert(bpf_map_lookup_elem(fd, &key, &value) == 0 &&
599 600 601 602 603 604 605 606 607
		       value == key);
	}

	/* Now let's delete all elemenets in parallel. */
	data[1] = DO_DELETE;
	run_parallel(TASKS, do_work, data);

	/* Nothing should be left. */
	key = -1;
608
	assert(bpf_map_get_next_key(fd, NULL, &key) == -1 && errno == ENOENT);
609
	assert(bpf_map_get_next_key(fd, &key, &key) == -1 && errno == ENOENT);
610 611 612 613 614 615
}

static void run_all_tests(void)
{
	test_hashmap(0, NULL);
	test_hashmap_percpu(0, NULL);
616
	test_hashmap_walk(0, NULL);
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642

	test_arraymap(0, NULL);
	test_arraymap_percpu(0, NULL);

	test_arraymap_percpu_many_keys();

	test_map_large();
	test_map_parallel();
	test_map_stress();
}

int main(void)
{
	struct rlimit rinf = { RLIM_INFINITY, RLIM_INFINITY };

	setrlimit(RLIMIT_MEMLOCK, &rinf);

	map_flags = 0;
	run_all_tests();

	map_flags = BPF_F_NO_PREALLOC;
	run_all_tests();

	printf("test_maps: OK\n");
	return 0;
}