Mercurial > hg > xemacs-beta
comparison lib-src/gnuclient.c @ 151:59463afc5666 r20-3b2
Import from CVS: tag r20-3b2
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:37:19 +0200 |
parents | 538048ae2ab8 |
children | 25f70ba0133c |
comparison
equal
deleted
inserted
replaced
150:8ebb1c0f0f6f | 151:59463afc5666 |
---|---|
122 Emacs, but I think it's better not to. I can see no reason why | 122 Emacs, but I think it's better not to. I can see no reason why |
123 Emacs should SIGSEGV whenever gnuclient SIGSEGV-s, etc. */ | 123 Emacs should SIGSEGV whenever gnuclient SIGSEGV-s, etc. */ |
124 signal (SIGHUP, pass_signal_to_emacs); | 124 signal (SIGHUP, pass_signal_to_emacs); |
125 signal (SIGQUIT, pass_signal_to_emacs); | 125 signal (SIGQUIT, pass_signal_to_emacs); |
126 signal (SIGINT, pass_signal_to_emacs); | 126 signal (SIGINT, pass_signal_to_emacs); |
127 #ifdef SIGWINCH | |
128 signal (SIGWINCH, pass_signal_to_emacs); | |
129 #endif | |
127 | 130 |
128 /* We want emacs to realize that we are resuming */ | 131 /* We want emacs to realize that we are resuming */ |
129 signal (SIGCONT, tell_emacs_to_resume); | 132 signal (SIGCONT, tell_emacs_to_resume); |
130 } | 133 } |
131 | 134 |
188 | 191 |
189 strcat (fullpath,filename); | 192 strcat (fullpath,filename); |
190 | 193 |
191 } /* filename_expand */ | 194 } /* filename_expand */ |
192 | 195 |
196 /* Encase the string in quotes, escape all the backslashes and quotes | |
197 in string. */ | |
198 char * | |
199 clean_string (CONST char *s) | |
200 { | |
201 int i = 0; | |
202 char *p, *res; | |
203 | |
204 for (p = s; *p; p++, i++) | |
205 { | |
206 if (*p == '\\' || *p == '\"') | |
207 ++i; | |
208 else if (*p == '\004') | |
209 i += 3; | |
210 } | |
211 p = res = (char *)malloc (i + 2 + 1); | |
212 *p++ = '\"'; | |
213 for (; *s; p++, s++) | |
214 { | |
215 switch (*s) | |
216 { | |
217 case '\\': | |
218 *p++ = '\\'; | |
219 *p = '\\'; | |
220 break; | |
221 case '\"': | |
222 *p++ = '\\'; | |
223 *p = '\"'; | |
224 break; | |
225 case '\004': | |
226 *p++ = '\\'; | |
227 *p++ = 'C'; | |
228 *p++ = '-'; | |
229 *p = 'd'; | |
230 break; | |
231 default: | |
232 *p = *s; | |
233 } | |
234 } | |
235 *p++ = '\"'; | |
236 *p = '\0'; | |
237 return res; | |
238 } | |
239 | |
240 #define GET_ARGUMENT(var, desc) do { \ | |
241 if (*(p + 1)) (var) = p + 1; \ | |
242 else \ | |
243 { \ | |
244 if (!argv[++i]) \ | |
245 { \ | |
246 fprintf (stderr, "%s: `%s' must be followed by an argument\n", \ | |
247 progname, desc); \ | |
248 exit (1); \ | |
249 } \ | |
250 (var) = argv[i]; \ | |
251 } \ | |
252 over = 1; \ | |
253 } while (0) | |
254 | |
255 | |
193 int | 256 int |
194 main (int argc, char *argv[]) | 257 main (int argc, char *argv[]) |
195 { | 258 { |
196 int starting_line = 1; /* line to start editing at */ | 259 int starting_line = 1; /* line to start editing at */ |
197 char command[MAXPATHLEN+50]; /* emacs command buffer */ | 260 char command[MAXPATHLEN+50]; /* emacs command buffer */ |
198 char fullpath[MAXPATHLEN+1]; /* full pathname to file */ | 261 char fullpath[MAXPATHLEN+1]; /* full pathname to file */ |
199 int qflg = 0; /* quick edit, don't wait for | 262 char *eval_form = NULL; /* form to evaluate with `-eval' */ |
200 * user to finish */ | 263 char *eval_function = NULL; /* function to evaluate with `-f' */ |
201 int view = 0; /* view only. */ | 264 char *load_library = NULL; /* library to load */ |
202 int errflg = 0; /* option error */ | 265 int quick = 0; /* quick edit, don't wait for user to |
203 int c; /* char from getopt */ | 266 finish */ |
204 int s; /* socket / msqid to server */ | 267 int batch = 0; /* batch mode */ |
205 int connect_type; /* CONN_UNIX, CONN_INTERNET, or | 268 int view = 0; /* view only. */ |
206 * CONN_IPC */ | 269 int nofiles = 0; |
270 int errflg = 0; /* option error */ | |
271 int c; /* char from getopt */ | |
272 int s; /* socket / msqid to server */ | |
273 int connect_type; /* CONN_UNIX, CONN_INTERNET, or | |
274 * CONN_IPC */ | |
207 int suppress_windows_system = 0; | 275 int suppress_windows_system = 0; |
208 char *display; | 276 char *display; |
209 #ifdef INTERNET_DOMAIN_SOCKETS | 277 #ifdef INTERNET_DOMAIN_SOCKETS |
210 char *hostarg = NULL; /* remote hostname */ | 278 char *hostarg = NULL; /* remote hostname */ |
211 char thishost[HOSTNAMSZ]; /* this hostname */ | 279 char *remotearg; |
212 char remotepath[MAXPATHLEN+1]; /* remote pathname */ | 280 char thishost[HOSTNAMSZ]; /* this hostname */ |
213 int rflg = 0; /* pathname given on cmdline */ | 281 char remotepath[MAXPATHLEN+1]; /* remote pathname */ |
214 u_short portarg = 0; /* port to server */ | 282 char *path; |
215 char *ptr; /* return from getenv */ | 283 int rflg = 0; /* pathname given on cmdline */ |
284 char *portarg; | |
285 u_short port = 0; /* port to server */ | |
216 #endif /* INTERNET_DOMAIN_SOCKETS */ | 286 #endif /* INTERNET_DOMAIN_SOCKETS */ |
217 #ifdef SYSV_IPC | 287 #ifdef SYSV_IPC |
218 struct msgbuf *msgp; /* message */ | 288 struct msgbuf *msgp; /* message */ |
219 #endif /* SYSV_IPC */ | 289 #endif /* SYSV_IPC */ |
220 char *tty; | 290 char *tty; |
221 char buffer[GSERV_BUFSZ+1]; /* buffer to read pid */ | 291 char buffer[GSERV_BUFSZ + 1]; /* buffer to read pid */ |
292 char result[GSERV_BUFSZ + 1]; | |
293 int i; | |
222 | 294 |
223 #ifdef INTERNET_DOMAIN_SOCKETS | 295 #ifdef INTERNET_DOMAIN_SOCKETS |
224 memset (remotepath, 0, sizeof (remotepath)); | 296 memset (remotepath, 0, sizeof (remotepath)); |
225 #endif /* INTERNET_DOMAIN_SOCKETS */ | 297 #endif /* INTERNET_DOMAIN_SOCKETS */ |
226 | 298 |
227 progname = argv[0]; | 299 progname = strrchr (argv[0], '/'); |
300 if (progname) | |
301 ++progname; | |
302 else | |
303 progname = argv[0]; | |
228 | 304 |
229 display = getenv ("DISPLAY"); | 305 display = getenv ("DISPLAY"); |
230 if (!display) | 306 if (!display) |
231 suppress_windows_system = 1; | 307 suppress_windows_system = 1; |
232 | 308 |
233 while ((c = getopt (argc, argv, | 309 for (i = 1; argv[i] && !errflg; i++) |
234 | 310 { |
311 if (*argv[i] != '-') | |
312 break; | |
313 if (!strcmp (argv[i], "-batch")) | |
314 batch = 1; | |
315 else if (!strcmp (argv[i], "-eval")) | |
316 { | |
317 if (!argv[++i]) | |
318 { | |
319 fprintf (stderr, "%s: `-eval' must be followed by an argument\n", | |
320 progname); | |
321 exit (1); | |
322 } | |
323 eval_form = argv[i]; | |
324 } | |
325 else if (!strcmp (argv[i], "-display")) | |
326 { | |
327 suppress_windows_system = 0; | |
328 if (!argv[++i]) | |
329 { | |
330 fprintf (stderr, "%s: `-display' must be followed by an argument\n", | |
331 progname); | |
332 exit (1); | |
333 } | |
334 display = argv[i]; | |
335 } | |
336 else if (!strcmp (argv[i], "-nw")) | |
337 suppress_windows_system = 1; | |
338 else | |
339 { | |
340 /* Iterate over one-letter options. */ | |
341 char *p; | |
342 int over = 0; | |
343 for (p = argv[i] + 1; *p && !over; p++) | |
344 { | |
345 switch (*p) | |
346 { | |
347 case 'q': | |
348 quick = 1; | |
349 break; | |
350 case 'v': | |
351 view = 1; | |
352 break; | |
353 case 'f': | |
354 GET_ARGUMENT (eval_function, "-f"); | |
355 break; | |
356 case 'l': | |
357 GET_ARGUMENT (load_library, "-l"); | |
358 break; | |
235 #ifdef INTERNET_DOMAIN_SOCKETS | 359 #ifdef INTERNET_DOMAIN_SOCKETS |
236 "n:h:p:r:qv" | 360 case 'h': |
237 #else /* !INTERNET_DOMAIN_SOCKETS */ | 361 GET_ARGUMENT (hostarg, "-h"); |
238 "n:qv" | 362 break; |
239 #endif /* !INTERNET_DOMAIN_SOCKETS */ | 363 case 'p': |
240 | 364 GET_ARGUMENT (portarg, "-p"); |
241 )) != EOF) | 365 port = atoi (portarg); |
242 switch (c) | 366 break; |
243 { | 367 case 'r': |
244 case 'n': | 368 GET_ARGUMENT (remotearg, "-r"); |
245 if (*optarg == 'w') | 369 strcpy (remotepath, remotearg); |
246 suppress_windows_system++; | 370 rflg = 1; |
247 else | 371 break; |
248 errflg++; | |
249 break; | |
250 case 'q': /* quick mode specified */ | |
251 qflg++; | |
252 break; | |
253 case 'v': | |
254 view++; | |
255 break; | |
256 | |
257 #ifdef INTERNET_DOMAIN_SOCKETS | |
258 case 'h': /* server host name specified */ | |
259 hostarg = optarg; | |
260 break; | |
261 case 'r': /* remote path from server specifed */ | |
262 strcpy (remotepath,optarg); | |
263 rflg++; | |
264 break; | |
265 case 'p': /* port number specified */ | |
266 portarg = atoi (optarg); | |
267 break; | |
268 #endif /* INTERNET_DOMAIN_SOCKETS */ | 372 #endif /* INTERNET_DOMAIN_SOCKETS */ |
269 | 373 default: |
270 case '?': | 374 errflg = 1; |
271 errflg++; | 375 } |
272 } /* switch */ | 376 } /* for */ |
377 } /* else */ | |
378 } /* for */ | |
273 | 379 |
274 if (errflg) | 380 if (errflg) |
275 { | 381 { |
276 fprintf (stderr, | 382 fprintf (stderr, |
277 #ifdef INTERNET_DOMAIN_SOCKETS | 383 #ifdef INTERNET_DOMAIN_SOCKETS |
278 "usage: %s [-q] [-h hostname] [-p port] [-r pathname] " | 384 "usage: %s [-nw] [-q] [-v] [-l library] [-f function] [-eval expr]\n" |
385 " [-h host] [-p port] [-r file-name] [[+line] file] ...\n", | |
386 #else /* !INTERNET_DOMAIN_SOCKETS */ | |
387 "usage: %s [-nw] [-q] [-v] [-l library] [-f function] [-eval expr] " | |
279 "[[+line] path] ...\n", | 388 "[[+line] path] ...\n", |
280 #else /* !INTERNET_DOMAIN_SOCKETS */ | |
281 "usage: %s [-nw] [-q] [[+line] path] ...\n", | |
282 #endif /* !INTERNET_DOMAIN_SOCKETS */ | 389 #endif /* !INTERNET_DOMAIN_SOCKETS */ |
283 progname); | 390 progname); |
284 exit (1); | 391 exit (1); |
285 } /* if */ | |
286 | |
287 if (suppress_windows_system) | |
288 { | |
289 tty = ttyname (0); | |
290 if (!tty) | |
291 { | |
292 fprintf (stderr, "%s: Not connected to a tty", progname); | |
293 exit (1); | |
294 } | |
295 } | 392 } |
296 /* This next stuff added in an attempt to make handling of the tty | 393 if (batch && argv[i]) |
297 do the right thing when dealing with signals. The idea is to | 394 { |
298 pass all the appropriate signals to the emacs process. */ | 395 fprintf (stderr, "%s: Cannot specify `-batch' with file names\n", |
299 | |
300 connect_type = make_connection (NULL, (u_short) 0, &s); | |
301 | |
302 send_string (s, "(gnuserv-eval '(emacs-pid))"); | |
303 send_string (s, EOT_STR); | |
304 | |
305 if (read_line (s, buffer) == 0) | |
306 { | |
307 fprintf (stderr, "%s: Could not establish emacs procces id\n", | |
308 progname); | 396 progname); |
309 exit (1); | 397 exit (1); |
310 } | 398 } |
311 /* Don't do disconnect_from_server becasue we have already read | 399 *result = '\0'; |
312 data, and disconnect doesn't do anything else. */ | 400 if (eval_function || eval_form || load_library) |
313 #ifdef SYSV_IPC | 401 { |
314 if (connect_type == (int) CONN_IPC) | 402 #if defined(INTERNET_DOMAIN_SOCKETS) |
315 disconnect_from_ipc_server (s, msgp, FALSE); | 403 connect_type = make_connection (hostarg, port, &s); |
404 #else | |
405 connect_type = make_connection (NULL, (u_short) 0, &s); | |
406 #endif | |
407 sprintf (command, "(gnuserv-eval%s '(progn ", quick ? "-quickly" : ""); | |
408 send_string (s, command); | |
409 if (load_library) | |
410 { | |
411 sprintf (command, " (load-library %s)", clean_string (load_library)); | |
412 send_string (s, command); | |
413 } | |
414 if (eval_form) | |
415 { | |
416 sprintf (command, " %s", eval_form); | |
417 send_string (s, command); | |
418 } | |
419 if (eval_function) | |
420 { | |
421 sprintf (command, " (%s)", eval_function); | |
422 send_string (s, command); | |
423 } | |
424 send_string (s, "))"); | |
425 send_string (s, EOT_STR); | |
426 if (read_line (s, result) == 0) | |
427 { | |
428 fprintf (stderr, "%s: Could not read\n", progname); | |
429 exit (1); | |
430 } | |
431 } /* eval_function || eval_form || load_library */ | |
432 else if (batch) | |
433 { | |
434 fprintf (stderr, "%s: `-batch' requires an evaluation\n", | |
435 progname); | |
436 exit (1); | |
437 } | |
438 | |
439 if (!batch) | |
440 { | |
441 if (suppress_windows_system) | |
442 { | |
443 tty = ttyname (0); | |
444 if (!tty) | |
445 { | |
446 fprintf (stderr, "%s: Not connected to a tty", progname); | |
447 exit (1); | |
448 } | |
449 } | |
450 | |
451 #if defined(INTERNET_DOMAIN_SOCKETS) | |
452 connect_type = make_connection (hostarg, port, &s); | |
453 #else | |
454 connect_type = make_connection (NULL, (u_short) 0, &s); | |
455 #endif | |
456 | |
457 send_string (s, "(gnuserv-eval '(emacs-pid))"); | |
458 send_string (s, EOT_STR); | |
459 | |
460 if (read_line (s, buffer) == 0) | |
461 { | |
462 fprintf (stderr, "%s: Could not establish emacs procces id\n", | |
463 progname); | |
464 exit (1); | |
465 } | |
466 /* Don't do disconnect_from_server becasue we have already read | |
467 data, and disconnect doesn't do anything else. */ | |
468 #ifndef INTERNET_DOMAIN_SOCKETS | |
469 if (connect_type == (int) CONN_IPC) | |
470 disconnect_from_ipc_server (s, msgp, FALSE); | |
316 #endif /* !SYSV_IPC */ | 471 #endif /* !SYSV_IPC */ |
317 | 472 |
318 emacs_pid = (pid_t)atol(buffer); | 473 emacs_pid = (pid_t)atol(buffer); |
319 initialize_signals(); | 474 initialize_signals(); |
320 | 475 |
321 #if defined(INTERNET_DOMAIN_SOCKETS) && !defined(GNUATTACH) | 476 #if defined(INTERNET_DOMAIN_SOCKETS) |
322 connect_type = make_connection (hostarg, portarg, &s); | 477 connect_type = make_connection (hostarg, port, &s); |
323 #else | 478 #else |
324 connect_type = make_connection (NULL, (u_short) 0, &s); | 479 connect_type = make_connection (NULL, (u_short) 0, &s); |
325 #endif | 480 #endif |
326 | 481 |
327 #ifdef INTERNET_DOMAIN_SOCKETS | 482 #ifdef INTERNET_DOMAIN_SOCKETS |
328 if (connect_type == (int) CONN_INTERNET) | 483 if (connect_type == (int) CONN_INTERNET) |
329 { | 484 { |
330 gethostname (thishost, HOSTNAMSZ); | 485 char *ptr; |
331 if (!rflg) | 486 gethostname (thishost, HOSTNAMSZ); |
332 { /* attempt to generate a path | 487 if (!rflg) |
488 { /* attempt to generate a path | |
333 * to this machine */ | 489 * to this machine */ |
334 if ((ptr = getenv ("GNU_NODE")) != NULL) | 490 if ((ptr = getenv ("GNU_NODE")) != NULL) |
335 /* user specified a path */ | 491 /* user specified a path */ |
336 strcpy (remotepath, ptr); | 492 strcpy (remotepath, ptr); |
337 } | 493 } |
338 #if 0 /* This is really bogus... re-enable it if you must have it! */ | 494 #if 0 /* This is really bogus... re-enable it if you must have it! */ |
339 #if defined (hp9000s300) || defined (hp9000s800) | 495 #if defined (hp9000s300) || defined (hp9000s800) |
340 else if (strcmp (thishost,hostarg)) | 496 else if (strcmp (thishost,hostarg)) |
341 { /* try /net/thishost */ | 497 { /* try /net/thishost */ |
342 strcpy (remotepath, "/net/"); /* (this fails using internet | 498 strcpy (remotepath, "/net/"); /* (this fails using internet |
343 addresses) */ | 499 addresses) */ |
344 strcat (remotepath, thishost); | 500 strcat (remotepath, thishost); |
345 } | 501 } |
346 #endif | 502 #endif |
347 #endif | 503 #endif |
348 } | 504 } |
349 else | 505 else |
350 { /* same machines, no need for path */ | 506 { /* same machines, no need for path */ |
351 remotepath[0] = '\0'; /* default is the empty path */ | 507 remotepath[0] = '\0'; /* default is the empty path */ |
352 } | 508 } |
353 #endif /* INTERNET_DOMAIN_SOCKETS */ | 509 #endif /* INTERNET_DOMAIN_SOCKETS */ |
354 | 510 |
355 #ifdef SYSV_IPC | 511 #ifdef SYSV_IPC |
356 if ((msgp = (struct msgbuf *) | 512 if ((msgp = (struct msgbuf *) |
357 malloc (sizeof *msgp + GSERV_BUFSZ)) == NULL) | 513 malloc (sizeof *msgp + GSERV_BUFSZ)) == NULL) |
358 { | 514 { |
359 fprintf (stderr, "%s: not enough memory for message buffer\n", progname); | 515 fprintf (stderr, "%s: not enough memory for message buffer\n", progname); |
360 exit (1); | 516 exit (1); |
361 } /* if */ | 517 } /* if */ |
362 | 518 |
363 msgp->mtext[0] = '\0'; /* ready for later strcats */ | 519 msgp->mtext[0] = '\0'; /* ready for later strcats */ |
364 #endif /* SYSV_IPC */ | 520 #endif /* SYSV_IPC */ |
365 | 521 |
366 if (suppress_windows_system) | 522 if (suppress_windows_system) |
367 { | 523 { |
368 ptr = getenv ("TERM"); | 524 char *term = getenv ("TERM"); |
369 if (!ptr) | 525 if (!term) |
370 { | 526 { |
371 fprintf (stderr, "%s: unknown terminal type\n", progname); | 527 fprintf (stderr, "%s: unknown terminal type\n", progname); |
372 exit (1); | 528 exit (1); |
373 } | 529 } |
374 sprintf (command, | 530 sprintf (command, "(gnuserv-edit-files '(tty %s %s %d) '(", |
375 "(gnuserv-edit-files '(tty \"%s\" \"%s\" %d) '(", | 531 clean_string (tty), clean_string (term), getpid ()); |
376 tty, ptr, getpid ()); | 532 } |
377 } | 533 else /* !suppress_windows_system */ |
378 else /* !suppress_windows_system */ | 534 { |
379 { | 535 sprintf (command, "(gnuserv-edit-files '(x %s) '(", |
380 sprintf (command, "(gnuserv-edit-files '(x \"%s\") '(", | 536 clean_string (display)); |
381 display); | 537 } /* !suppress_windows_system */ |
382 } /* !suppress_windows_system */ | 538 send_string (s, command); |
383 send_string (s, command); | 539 |
384 | 540 if (!argv[i]) |
385 if (!suppress_windows_system && (optind == argc)) | 541 nofiles = 1; |
386 qflg = 1; | 542 |
387 | 543 for (; argv[i]; i++) |
388 for (; optind < argc; optind++) | 544 { |
389 { | 545 if (i < argc - 1 && *argv[i] == '+') |
390 if (optind < argc - 1 && *argv[optind] == '+') | 546 starting_line = atoi (argv[i++]); |
391 starting_line = atoi (argv[optind++]); | 547 else |
392 else | 548 starting_line = 1; |
393 starting_line = 1; | 549 /* If the last argument is +something, treat it as a file. */ |
394 /* If the last argument is +something, treat it as a file. */ | 550 if (i == argc) |
395 if (optind == argc) | 551 { |
396 { | 552 starting_line = 1; |
397 starting_line = 1; | 553 --i; |
398 --optind; | 554 } |
399 } | 555 filename_expand (fullpath, argv[i]); |
400 filename_expand (fullpath, argv[optind]); | |
401 sprintf (command, "(%d . \"%s%s\")", starting_line, | |
402 #ifdef INTERNET_DOMAIN_SOCKETS | 556 #ifdef INTERNET_DOMAIN_SOCKETS |
403 remotepath, | 557 path = malloc (strlen (remotepath) + strlen (fullpath) + 1); |
404 #else /* !INTERNET_DOMAIN_SOCKETS */ | 558 sprintf (path, "%s%s", remotepath, fullpath); |
405 "", | 559 #else |
406 #endif | 560 path = malloc (strlen (fullpath)); |
407 fullpath); | 561 strcpy (path, fullpath); |
562 #endif | |
563 sprintf (command, "(%d . %s)", starting_line, clean_string (path)); | |
564 send_string (s, command); | |
565 free (path); | |
566 } /* for */ | |
567 | |
568 sprintf (command, ")%s%s", | |
569 (quick || (nofiles && !suppress_windows_system)) ? " 'quick" : "", | |
570 view ? " 'view" : ""); | |
408 send_string (s, command); | 571 send_string (s, command); |
409 } /* for */ | 572 send_string (s, ")"); |
410 | |
411 sprintf (command, ") %s)", qflg ? "'quick" : (view ? "'view" : "")); | |
412 send_string (s, command); | |
413 | 573 |
414 #ifdef SYSV_IPC | 574 #ifdef SYSV_IPC |
415 if (connect_type == (int) CONN_IPC) | 575 if (connect_type == (int) CONN_IPC) |
416 disconnect_from_ipc_server (s, msgp, FALSE); | 576 disconnect_from_ipc_server (s, msgp, FALSE); |
417 #else /* !SYSV_IPC */ | 577 #else /* !SYSV_IPC */ |
418 if (connect_type != (int) CONN_IPC) | 578 if (connect_type != (int) CONN_IPC) |
419 disconnect_from_server (s, FALSE); | 579 disconnect_from_server (s, FALSE); |
420 #endif /* !SYSV_IPC */ | 580 #endif /* !SYSV_IPC */ |
581 } /* not batch */ | |
582 | |
583 if (batch && !quick) | |
584 printf ("%s\n", result); | |
421 | 585 |
422 return 0; | 586 return 0; |
423 | 587 |
424 } /* main */ | 588 } /* main */ |
425 | 589 |