io.c 13.1 KB
Newer Older
Ondřej Zajíček's avatar
Ondřej Zajíček committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 *	BIRD -- I/O and event loop
 *
 *	Can be freely distributed and used under the terms of the GNU GPL.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>

#include "nest/bird.h"
#include "proto/bfd/io.h"
19 20 21

#include "lib/buffer.h"
#include "lib/heap.h"
Ondřej Zajíček's avatar
Ondřej Zajíček committed
22 23 24 25 26
#include "lib/lists.h"
#include "lib/resource.h"
#include "lib/event.h"
#include "lib/socket.h"

27 28 29 30

struct birdloop
{
  pool *pool;
Ondřej Zajíček's avatar
Ondřej Zajíček committed
31 32
  pthread_t thread;
  pthread_mutex_t mutex;
33

Ondřej Zajíček's avatar
Ondřej Zajíček committed
34 35
  btime last_time;
  btime real_time;
36 37
  u8 use_monotonic_clock;

38
  u8 stop_called;
Ondřej Zajíček's avatar
Ondřej Zajíček committed
39 40 41 42
  u8 poll_active;
  u8 wakeup_masked;
  int wakeup_fds[2];

43 44 45 46 47 48 49 50 51 52 53 54
  BUFFER(timer2 *) timers;
  list event_list;
  list sock_list;
  uint sock_num;

  BUFFER(sock *) poll_sk;
  BUFFER(struct pollfd) poll_fd;
  u8 poll_changed;
  u8 close_scheduled;
};


55 56 57
/*
 *	Current thread context
 */
Ondřej Zajíček's avatar
Ondřej Zajíček committed
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79

static pthread_key_t current_loop_key;

static inline struct birdloop *
birdloop_current(void)
{
  return pthread_getspecific(current_loop_key);
}

static inline void
birdloop_set_current(struct birdloop *loop)
{
  pthread_setspecific(current_loop_key, loop);
}

static inline void
birdloop_init_current(void)
{
  pthread_key_create(&current_loop_key, NULL);
}


80 81 82
/*
 *	Time clock
 */
Ondřej Zajíček's avatar
Ondřej Zajíček committed
83

84 85
static void times_update_alt(struct birdloop *loop);

Ondřej Zajíček's avatar
Ondřej Zajíček committed
86
static void
87 88 89 90 91 92 93 94
times_init(struct birdloop *loop)
{
  struct timespec ts;
  int rv;

  rv = clock_gettime(CLOCK_MONOTONIC, &ts);
  if (rv < 0)
  {
95
    log(L_WARN "Monotonic clock is missing");
96 97 98 99 100 101 102 103

    loop->use_monotonic_clock = 0;
    loop->last_time = 0;
    loop->real_time = 0;
    times_update_alt(loop);
    return;
  }

Ondřej Zajíček's avatar
Ondřej Zajíček committed
104
  if ((ts.tv_sec < 0) || (((s64) ts.tv_sec) > ((s64) 1 << 40)))
105 106 107
    log(L_WARN "Monotonic clock is crazy");

  loop->use_monotonic_clock = 1;
108
  loop->last_time = ((s64) ts.tv_sec S) + (ts.tv_nsec / 1000);
109 110 111 112 113 114 115 116 117 118 119 120 121
  loop->real_time = 0;
}

static void
times_update_pri(struct birdloop *loop)
{
  struct timespec ts;
  int rv;

  rv = clock_gettime(CLOCK_MONOTONIC, &ts);
  if (rv < 0)
    die("clock_gettime: %m");

122
  btime new_time = ((s64) ts.tv_sec S) + (ts.tv_nsec / 1000);
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140

  if (new_time < loop->last_time)
    log(L_ERR "Monotonic clock is broken");

  loop->last_time = new_time;
  loop->real_time = 0;
}

static void
times_update_alt(struct birdloop *loop)
{
  struct timeval tv;
  int rv;

  rv = gettimeofday(&tv, NULL);
  if (rv < 0)
    die("gettimeofday: %m");

141
  btime new_time = ((s64) tv.tv_sec S) + tv.tv_usec;
Ondřej Zajíček's avatar
Ondřej Zajíček committed
142
  btime delta = new_time - loop->real_time;
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164

  if ((delta < 0) || (delta > (60 S)))
  {
    if (loop->real_time)
      log(L_WARN "Time jump, delta %d us", (int) delta);

    delta = 100 MS;
  }

  loop->last_time += delta;
  loop->real_time = new_time;
}

static void
times_update(struct birdloop *loop)
{
  if (loop->use_monotonic_clock)
    times_update_pri(loop);
  else
    times_update_alt(loop);
}

Ondřej Zajíček's avatar
Ondřej Zajíček committed
165 166 167 168 169 170
btime
current_time(void)
{
  return birdloop_current()->last_time;
}

171

172 173 174
/*
 *	Wakeup code for birdloop
 */
175 176 177 178

static void
pipe_new(int *pfds)
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
179
  int rv = pipe(pfds);
180 181 182 183 184 185 186 187 188 189
  if (rv < 0)
    die("pipe: %m");

  if (fcntl(pfds[0], F_SETFL, O_NONBLOCK) < 0)
    die("fcntl(O_NONBLOCK): %m");

  if (fcntl(pfds[1], F_SETFL, O_NONBLOCK) < 0)
    die("fcntl(O_NONBLOCK): %m");
}

Ondřej Zajíček's avatar
Ondřej Zajíček committed
190 191
void
pipe_drain(int fd)
192 193 194 195 196
{
  char buf[64];
  int rv;
  
 try:
Ondřej Zajíček's avatar
Ondřej Zajíček committed
197
  rv = read(fd, buf, 64);
198 199 200 201 202 203 204 205 206 207 208 209
  if (rv < 0)
  {
    if (errno == EINTR)
      goto try;
    if (errno == EAGAIN)
      return;
    die("wakeup read: %m");
  }
  if (rv == 64)
    goto try;
}

Ondřej Zajíček's avatar
Ondřej Zajíček committed
210 211
void
pipe_kick(int fd)
212 213 214 215 216
{
  u64 v = 1;
  int rv;

 try:
Ondřej Zajíček's avatar
Ondřej Zajíček committed
217
  rv = write(fd, &v, sizeof(u64));
218 219 220 221 222 223 224 225 226 227
  if (rv < 0)
  {
    if (errno == EINTR)
      goto try;
    if (errno == EAGAIN)
      return;
    die("wakeup write: %m");
  }
}

Ondřej Zajíček's avatar
Ondřej Zajíček committed
228 229 230 231 232
static inline void
wakeup_init(struct birdloop *loop)
{
  pipe_new(loop->wakeup_fds);
}
233

Ondřej Zajíček's avatar
Ondřej Zajíček committed
234 235 236 237 238
static inline void
wakeup_drain(struct birdloop *loop)
{
  pipe_drain(loop->wakeup_fds[0]);
}
239

Ondřej Zajíček's avatar
Ondřej Zajíček committed
240 241 242 243 244
static inline void
wakeup_do_kick(struct birdloop *loop) 
{
  pipe_kick(loop->wakeup_fds[1]);
}
245

Ondřej Zajíček's avatar
Ondřej Zajíček committed
246 247 248 249 250 251 252 253
static inline void
wakeup_kick(struct birdloop *loop)
{
  if (!loop->wakeup_masked)
    wakeup_do_kick(loop);
  else
    loop->wakeup_masked = 2;
}
254

Ondřej Zajíček's avatar
Ondřej Zajíček committed
255

256 257 258
/*
 *	Events
 */
Ondřej Zajíček's avatar
Ondřej Zajíček committed
259 260 261 262 263 264 265 266

static inline uint
events_waiting(struct birdloop *loop)
{
  return !EMPTY_LIST(loop->event_list);
}

static inline void
267 268
events_init(struct birdloop *loop)
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
269
  init_list(&loop->event_list);
270 271 272 273 274 275 276 277 278 279 280 281
}

static void
events_fire(struct birdloop *loop)
{
  times_update(loop);
  ev_run_list(&loop->event_list);
}

void
ev2_schedule(event *e)
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
282 283
  struct birdloop *loop = birdloop_current();

284 285 286 287 288 289 290 291 292 293
  if (loop->poll_active && EMPTY_LIST(loop->event_list))
    wakeup_kick(loop);

  if (e->n.next)
    rem_node(&e->n);

  add_tail(&loop->event_list, &e->n);
}


294 295 296
/*
 *	Timers
 */
Ondřej Zajíček's avatar
Ondřej Zajíček committed
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
#define TIMER_LESS(a,b)		((a)->expires < (b)->expires)
#define TIMER_SWAP(heap,a,b,t)	(t = heap[a], heap[a] = heap[b], heap[b] = t, \
				   heap[a]->index = (a), heap[b]->index = (b))

static inline uint timers_count(struct birdloop *loop)
{ return loop->timers.used - 1; }

static inline timer2 *timers_first(struct birdloop *loop)
{ return (loop->timers.used > 1) ? loop->timers.data[1] : NULL; }


static void
tm2_free(resource *r)
{
  timer2 *t = (timer2 *) r;

  tm2_stop(t);
}

static void
tm2_dump(resource *r)
{
  timer2 *t = (timer2 *) r;

  debug("(code %p, data %p, ", t->hook, t->data);
  if (t->randomize)
    debug("rand %d, ", t->randomize);
  if (t->recurrent)
    debug("recur %d, ", t->recurrent);
  if (t->expires)
Ondřej Zajíček's avatar
Ondřej Zajíček committed
328
    debug("expires in %d ms)\n", (t->expires - current_time()) TO_MS);
329 330 331 332
  else
    debug("inactive)\n");
}

Ondřej Zajíček's avatar
Ondřej Zajíček committed
333

334 335
static struct resclass tm2_class = {
  "Timer",
Ondřej Zajíček's avatar
Ondřej Zajíček committed
336
  sizeof(timer2),
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
  tm2_free,
  tm2_dump,
  NULL,
  NULL
};

timer2 *
tm2_new(pool *p)
{
  timer2 *t = ralloc(p, &tm2_class);
  t->index = -1;
  return t;
}

void
Ondřej Zajíček's avatar
Ondřej Zajíček committed
352
tm2_set(timer2 *t, btime when)
353
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
354
  struct birdloop *loop = birdloop_current();
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
  uint tc = timers_count(loop);

  if (!t->expires)
  {
    t->index = ++tc;
    t->expires = when;
    BUFFER_PUSH(loop->timers) = t;
    HEAP_INSERT(loop->timers.data, tc, timer2 *, TIMER_LESS, TIMER_SWAP);
  }
  else if (t->expires < when)
  {
    t->expires = when;
    HEAP_INCREASE(loop->timers.data, tc, timer2 *, TIMER_LESS, TIMER_SWAP, t->index);
  }
  else if (t->expires > when)
  {
    t->expires = when;
    HEAP_DECREASE(loop->timers.data, tc, timer2 *, TIMER_LESS, TIMER_SWAP, t->index);
  }

  if (loop->poll_active && (t->index == 1))
    wakeup_kick(loop);
}

Ondřej Zajíček's avatar
Ondřej Zajíček committed
379 380 381 382 383 384
void
tm2_start(timer2 *t, btime after)
{
  tm2_set(t, current_time() + MAX(after, 0));
}

385 386 387 388 389 390
void
tm2_stop(timer2 *t)
{
  if (!t->expires)
    return;

Ondřej Zajíček's avatar
Ondřej Zajíček committed
391 392 393
  struct birdloop *loop = birdloop_current();
  uint tc = timers_count(loop);

394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
  HEAP_DELETE(loop->timers.data, tc, timer2 *, TIMER_LESS, TIMER_SWAP, t->index);
  BUFFER_POP(loop->timers);

  t->index = -1;
  t->expires = 0;
}

static void
timers_init(struct birdloop *loop)
{
  BUFFER_INIT(loop->timers, loop->pool, 4);
  BUFFER_PUSH(loop->timers) = NULL;
}

static void
timers_fire(struct birdloop *loop)
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
411
  btime base_time;
412 413 414 415 416 417 418 419 420 421 422 423
  timer2 *t;

  times_update(loop);
  base_time = loop->last_time;

  while (t = timers_first(loop))
  {
    if (t->expires > base_time)
      return;

    if (t->recurrent)
    {
Ondřej Zajíček's avatar
Ondřej Zajíček committed
424
      btime when = t->expires + t->recurrent;
425
      
Ondřej Zajíček's avatar
Ondřej Zajíček committed
426 427
      if (when <= loop->last_time)
	when = loop->last_time + t->recurrent;
428

Ondřej Zajíček's avatar
Ondřej Zajíček committed
429 430
      if (t->randomize)
	when += random() % (t->randomize + 1);
431

Ondřej Zajíček's avatar
Ondřej Zajíček committed
432
      tm2_set(t, when);
433 434 435 436 437 438 439 440 441
    }
    else
      tm2_stop(t);

    t->hook(t);
  }
}


442 443 444
/*
 *	Sockets
 */
Ondřej Zajíček's avatar
Ondřej Zajíček committed
445

446 447 448
static void
sockets_init(struct birdloop *loop)
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
449 450
  init_list(&loop->sock_list);
  loop->sock_num = 0;
451 452 453

  BUFFER_INIT(loop->poll_sk, loop->pool, 4);
  BUFFER_INIT(loop->poll_fd, loop->pool, 4);
Ondřej Zajíček's avatar
Ondřej Zajíček committed
454
  loop->poll_changed = 1;	/* add wakeup fd */
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
}

static void
sockets_add(struct birdloop *loop, sock *s)
{
  add_tail(&loop->sock_list, &s->n);
  loop->sock_num++;

  s->index = -1;
  loop->poll_changed = 1;

  if (loop->poll_active)
    wakeup_kick(loop);
}

void
sk_start(sock *s)
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
473 474 475
  struct birdloop *loop = birdloop_current();

  sockets_add(loop, s);
476 477 478 479 480 481 482 483 484
}

static void
sockets_remove(struct birdloop *loop, sock *s)
{
  rem_node(&s->n);
  loop->sock_num--;

  if (s->index >= 0)
Ondřej Zajíček's avatar
Ondřej Zajíček committed
485
    loop->poll_sk.data[s->index] = NULL;
486 487 488 489 490 491 492 493 494 495

  s->index = -1;
  loop->poll_changed = 1;

  /* Wakeup moved to sk_stop() */
}

void
sk_stop(sock *s)
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
496 497 498
  struct birdloop *loop = birdloop_current();

  sockets_remove(loop, s);
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513

  if (loop->poll_active)
  {
    loop->close_scheduled = 1;
    wakeup_kick(loop);
  }
  else
    close(s->fd);

  s->fd = -1;
}

static inline uint sk_want_events(sock *s)
{ return (s->rx_hook ? POLLIN : 0) | ((s->ttx != s->tpos) ? POLLOUT : 0); }

514 515 516
/*
FIXME: this should be called from sock code

517 518 519 520
static void
sockets_update(struct birdloop *loop, sock *s)
{
  if (s->index >= 0)
Ondřej Zajíček's avatar
Ondřej Zajíček committed
521
    loop->poll_fd.data[s->index].events = sk_want_events(s);
522
}
523
*/
524 525 526 527 528 529 530 531 532 533 534 535

static void
sockets_prepare(struct birdloop *loop)
{
  BUFFER_SET(loop->poll_sk, loop->sock_num + 1);
  BUFFER_SET(loop->poll_fd, loop->sock_num + 1);

  struct pollfd *pfd = loop->poll_fd.data;
  sock **psk = loop->poll_sk.data;
  int i = 0;
  node *n;

Ondřej Zajíček's avatar
Ondřej Zajíček committed
536
  WALK_LIST(n, loop->sock_list)
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
  {
    sock *s = SKIP_BACK(sock, n, n);

    ASSERT(i < loop->sock_num);

    s->index = i;
    *psk = s;
    pfd->fd = s->fd;
    pfd->events = sk_want_events(s);
    pfd->revents = 0;

    pfd++;
    psk++;
    i++;
  }

  ASSERT(i == loop->sock_num);

  /* Add internal wakeup fd */
  *psk = NULL;
  pfd->fd = loop->wakeup_fds[0];
  pfd->events = POLLIN;
  pfd->revents = 0;

  loop->poll_changed = 0;
}

static void
sockets_close_fds(struct birdloop *loop)
{
  struct pollfd *pfd = loop->poll_fd.data;
  sock **psk = loop->poll_sk.data;
  int poll_num = loop->poll_fd.used - 1;

  int i;
  for (i = 0; i < poll_num; i++)
    if (psk[i] == NULL)
      close(pfd[i].fd);

  loop->close_scheduled = 0;
}

579
int sk_read(sock *s, int revents);
Ondřej Zajíček's avatar
Ondřej Zajíček committed
580
int sk_write(sock *s);
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607

static void
sockets_fire(struct birdloop *loop)
{
  struct pollfd *pfd = loop->poll_fd.data;
  sock **psk = loop->poll_sk.data;
  int poll_num = loop->poll_fd.used - 1;

  times_update(loop);

  /* Last fd is internal wakeup fd */
  if (pfd[loop->sock_num].revents & POLLIN)
    wakeup_drain(loop);

  int i;
  for (i = 0; i < poll_num; pfd++, psk++, i++)
  {
    int e = 1;

    if (! pfd->revents)
      continue;

    if (pfd->revents & POLLNVAL)
      die("poll: invalid fd %d", pfd->fd);

    if (pfd->revents & POLLIN)
      while (e && *psk && (*psk)->rx_hook)
608
	e = sk_read(*psk, 0);
609 610 611 612 613 614 615 616 617

    e = 1;
    if (pfd->revents & POLLOUT)
      while (e && *psk)
	e = sk_write(*psk);
  }
}


618 619 620
/*
 *	Birdloop
 */
Ondřej Zajíček's avatar
Ondřej Zajíček committed
621 622 623

static void * birdloop_main(void *arg);

624
struct birdloop *
625
birdloop_new(void)
626
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
627 628 629 630 631
  /* FIXME: this init should be elsewhere and thread-safe */
  static int init = 0;
  if (!init)
    { birdloop_init_current(); init = 1; }

632
  pool *p = rp_new(NULL, "Birdloop root");
633
  struct birdloop *loop = mb_allocz(p, sizeof(struct birdloop));
Ondřej Zajíček's avatar
Ondřej Zajíček committed
634 635
  loop->pool = p;
  pthread_mutex_init(&loop->mutex, NULL);
636 637 638 639 640 641 642 643

  times_init(loop);
  wakeup_init(loop);

  events_init(loop);
  timers_init(loop);
  sockets_init(loop);

644 645
  return loop;
}
Ondřej Zajíček's avatar
Ondřej Zajíček committed
646

647 648 649
void
birdloop_start(struct birdloop *loop)
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
650
  int rv = pthread_create(&loop->thread, NULL, birdloop_main, loop);
651 652 653
  if (rv)
    die("pthread_create(): %M", rv);
}
Ondřej Zajíček's avatar
Ondřej Zajíček committed
654

655 656 657 658 659 660 661 662 663 664 665
void
birdloop_stop(struct birdloop *loop)
{
  pthread_mutex_lock(&loop->mutex);
  loop->stop_called = 1;
  wakeup_do_kick(loop);
  pthread_mutex_unlock(&loop->mutex);

  int rv = pthread_join(loop->thread, NULL);
  if (rv)
    die("pthread_join(): %M", rv);
666 667
}

668 669 670 671 672 673
void
birdloop_free(struct birdloop *loop)
{
  rfree(loop->pool);
}

674

675 676 677
void
birdloop_enter(struct birdloop *loop)
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
678 679 680
  /* TODO: these functions could save and restore old context */
  pthread_mutex_lock(&loop->mutex);
  birdloop_set_current(loop);
681 682 683 684 685
}

void
birdloop_leave(struct birdloop *loop)
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
686 687 688
  /* TODO: these functions could save and restore old context */
  birdloop_set_current(NULL);
  pthread_mutex_unlock(&loop->mutex);
689 690
}

Ondřej Zajíček's avatar
Ondřej Zajíček committed
691 692 693 694 695 696 697
void
birdloop_mask_wakeups(struct birdloop *loop)
{
  pthread_mutex_lock(&loop->mutex);
  loop->wakeup_masked = 1;
  pthread_mutex_unlock(&loop->mutex);
}
698 699

void
Ondřej Zajíček's avatar
Ondřej Zajíček committed
700
birdloop_unmask_wakeups(struct birdloop *loop)
701
{
Ondřej Zajíček's avatar
Ondřej Zajíček committed
702 703 704 705 706 707 708 709 710 711 712
  pthread_mutex_lock(&loop->mutex);
  if (loop->wakeup_masked == 2)
    wakeup_do_kick(loop);
  loop->wakeup_masked = 0;
  pthread_mutex_unlock(&loop->mutex);
}

static void *
birdloop_main(void *arg)
{
  struct birdloop *loop = arg;
713
  timer2 *t;
Ondřej Zajíček's avatar
Ondřej Zajíček committed
714
  int rv, timeout;
715

Ondřej Zajíček's avatar
Ondřej Zajíček committed
716 717 718
  birdloop_set_current(loop);

  pthread_mutex_lock(&loop->mutex);
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
  while (1)
  {
    events_fire(loop);
    timers_fire(loop);

    times_update(loop);
    if (events_waiting(loop))
      timeout = 0;
    else if (t = timers_first(loop))
      timeout = (tm2_remains(t) TO_MS) + 1;
    else
      timeout = -1;

    if (loop->poll_changed)
      sockets_prepare(loop);

    loop->poll_active = 1;
Ondřej Zajíček's avatar
Ondřej Zajíček committed
736
    pthread_mutex_unlock(&loop->mutex);
737 738 739 740 741 742 743 744 745 746

  try:
    rv = poll(loop->poll_fd.data, loop->poll_fd.used, timeout);
    if (rv < 0)
    {
      if (errno == EINTR || errno == EAGAIN)
	goto try;
      die("poll: %m");
    }

Ondřej Zajíček's avatar
Ondřej Zajíček committed
747
    pthread_mutex_lock(&loop->mutex);
748 749 750 751 752
    loop->poll_active = 0;

    if (loop->close_scheduled)
      sockets_close_fds(loop);

753 754 755
    if (loop->stop_called)
      break;

756 757 758 759 760
    if (rv)
      sockets_fire(loop);

    timers_fire(loop);
  }
Ondřej Zajíček's avatar
Ondřej Zajíček committed
761

762 763 764
  loop->stop_called = 0;
  pthread_mutex_unlock(&loop->mutex);

Ondřej Zajíček's avatar
Ondřej Zajíček committed
765
  return NULL;
766 767 768
}