Commit | Line | Data |
---|---|---|
31b8bf0a | 1 | #ifndef lint |
ca67e7b4 | 2 | static char sccsid[] = "@(#)fio.c 5.5 (Berkeley) 4/5/88"; |
31b8bf0a JB |
3 | #endif |
4 | ||
5 | /* | |
6 | * flow control protocol. | |
7 | * | |
8 | * This protocol relies on flow control of the data stream. | |
9 | * It is meant for working over links that can (almost) be | |
10 | * guaranteed to be errorfree, specifically X.25/PAD links. | |
11 | * A sumcheck is carried out over a whole file only. If a | |
12 | * transport fails the receiver can request retransmission(s). | |
13 | * This protocol uses a 7-bit datapath only, so it can be | |
14 | * used on links that are not 8-bit transparent. | |
15 | * | |
16 | * When using this protocol with an X.25 PAD: | |
17 | * Although this protocol uses no control chars except CR, | |
18 | * control chars NULL and ^P are used before this protocol | |
19 | * is started; since ^P is the default char for accessing | |
20 | * PAD X.28 command mode, be sure to disable that access | |
21 | * (PAD par 1). Also make sure both flow control pars | |
22 | * (5 and 12) are set. The CR used in this proto is meant | |
23 | * to trigger packet transmission, hence par 3 should be | |
24 | * set to 2; a good value for the Idle Timer (par 4) is 10. | |
25 | * All other pars should be set to 0. | |
9d469936 | 26 | * |
31b8bf0a JB |
27 | * Normally a calling site will take care of setting the |
28 | * local PAD pars via an X.28 command and those of the remote | |
29 | * PAD via an X.29 command, unless the remote site has a | |
30 | * special channel assigned for this protocol with the proper | |
31 | * par settings. | |
32 | * | |
9d469936 JB |
33 | * Additional comments for hosts with direct X.25 access: |
34 | * - the global variable IsTcpIp, when set, excludes the ioctl's, | |
35 | * so the same binary can run on X.25 and non-X.25 hosts; | |
36 | * - reads are done in small chunks, which can be smaller than | |
37 | * the packet size; your X.25 driver must support that. | |
38 | * | |
39 | * | |
40 | * Author: | |
41 | * Piet Beertema, CWI, Amsterdam, Sep 1984 | |
42 | * Modified for X.25 hosts: | |
43 | * Robert Elz, Melbourne Univ, Mar 1985 | |
31b8bf0a JB |
44 | */ |
45 | ||
31b8bf0a | 46 | #include "uucp.h" |
9d469936 | 47 | #include <signal.h> |
31b8bf0a JB |
48 | #ifdef USG |
49 | #include <termio.h> | |
50 | #else !USG | |
51 | #include <sgtty.h> | |
52 | #endif !USG | |
53 | #include <setjmp.h> | |
54 | ||
0aec7641 | 55 | #define FIBUFSIZ 4096 /* for X.25 interfaces: set equal to packet size, |
9d469936 JB |
56 | * but see comment above |
57 | */ | |
58 | ||
0aec7641 | 59 | #define FOBUFSIZ 4096 /* for X.25 interfaces: set equal to packet size; |
9d469936 JB |
60 | * otherwise make as large as feasible to reduce |
61 | * number of write system calls | |
62 | */ | |
31b8bf0a JB |
63 | |
64 | #ifndef MAXMSGLEN | |
65 | #define MAXMSGLEN BUFSIZ | |
66 | #endif MAXMSGLEN | |
67 | ||
68 | static int fchksum; | |
69 | static jmp_buf Ffailbuf; | |
70 | ||
0aec7641 RA |
71 | extern long Bytes_Sent, Bytes_Received; |
72 | ||
31b8bf0a JB |
73 | static |
74 | falarm() | |
75 | { | |
76 | signal(SIGALRM, falarm); | |
77 | longjmp(Ffailbuf, 1); | |
78 | } | |
79 | ||
80 | static int (*fsig)(); | |
81 | ||
82 | #ifndef USG | |
83 | #define TCGETA TIOCGETP | |
87d365c2 | 84 | #define TCSETAF TIOCSETP |
31b8bf0a JB |
85 | #define termio sgttyb |
86 | #endif USG | |
0aec7641 | 87 | static struct termio ttbuf; |
31b8bf0a JB |
88 | |
89 | fturnon() | |
90 | { | |
0aec7641 | 91 | int ttbuf_flags; |
31b8bf0a | 92 | |
9d469936 JB |
93 | if (!IsTcpIp) { |
94 | ioctl(Ifn, TCGETA, &ttbuf); | |
31b8bf0a | 95 | #ifdef USG |
0aec7641 | 96 | ttbuf_flags = ttbuf.c_iflag; |
9d469936 JB |
97 | ttbuf.c_iflag = IXOFF|IXON|ISTRIP; |
98 | ttbuf.c_cc[VMIN] = FIBUFSIZ > 64 ? 64 : FIBUFSIZ; | |
99 | ttbuf.c_cc[VTIME] = 5; | |
87d365c2 RA |
100 | if (ioctl(Ifn, TCSETAF, &ttbuf) < 0) { |
101 | syslog(LOG_ERR, "ioctl(TCSETAF) failed: %m"); | |
102 | cleanup(FAIL); | |
103 | } | |
0aec7641 | 104 | ttbuf.c_iflag = ttbuf_flags; |
9d469936 | 105 | #else !USG |
0aec7641 RA |
106 | ttbuf_flags = ttbuf.sg_flags; |
107 | ttbuf.sg_flags = ANYP|CBREAK; | |
87d365c2 RA |
108 | if (ioctl(Ifn, TCSETAF, &ttbuf) < 0) { |
109 | syslog(LOG_ERR, "ioctl(TCSETAF) failed: %m"); | |
110 | cleanup(FAIL); | |
111 | } | |
0aec7641 RA |
112 | /* this is two seperate ioctls to set the x.29 params */ |
113 | ttbuf.sg_flags |= TANDEM; | |
87d365c2 RA |
114 | if (ioctl(Ifn, TCSETAF, &ttbuf) < 0) { |
115 | syslog(LOG_ERR, "ioctl(TCSETAF) failed: %m"); | |
116 | cleanup(FAIL); | |
117 | } | |
0aec7641 RA |
118 | ttbuf.sg_flags = ttbuf_flags; |
119 | #endif USG | |
9d469936 | 120 | } |
31b8bf0a JB |
121 | fsig = signal(SIGALRM, falarm); |
122 | /* give the other side time to perform its ioctl; | |
123 | * otherwise it may flush out the first data this | |
124 | * side is about to send. | |
125 | */ | |
126 | sleep(2); | |
127 | return SUCCESS; | |
128 | } | |
129 | ||
130 | fturnoff() | |
131 | { | |
0aec7641 | 132 | if (!IsTcpIp) |
87d365c2 | 133 | ioctl(Ifn, TCSETAF, &ttbuf); |
31b8bf0a | 134 | (void) signal(SIGALRM, fsig); |
0aec7641 | 135 | sleep(2); |
31b8bf0a JB |
136 | return SUCCESS; |
137 | } | |
138 | ||
139 | fwrmsg(type, str, fn) | |
140 | register char *str; | |
141 | int fn; | |
142 | char type; | |
143 | { | |
144 | register char *s; | |
145 | char bufr[MAXMSGLEN]; | |
146 | ||
147 | s = bufr; | |
148 | *s++ = type; | |
149 | while (*str) | |
150 | *s++ = *str++; | |
151 | if (*(s-1) == '\n') | |
152 | s--; | |
153 | *s++ = '\r'; | |
9d469936 | 154 | *s = 0; |
31b8bf0a JB |
155 | (void) write(fn, bufr, s - bufr); |
156 | return SUCCESS; | |
157 | } | |
158 | ||
159 | frdmsg(str, fn) | |
160 | register char *str; | |
161 | register int fn; | |
162 | { | |
163 | register char *smax; | |
164 | ||
165 | if (setjmp(Ffailbuf)) | |
166 | return FAIL; | |
167 | smax = str + MAXMSGLEN - 1; | |
168 | (void) alarm(2*MAXMSGTIME); | |
169 | for (;;) { | |
170 | if (read(fn, str, 1) <= 0) | |
171 | goto msgerr; | |
9d469936 | 172 | *str &= 0177; |
31b8bf0a JB |
173 | if (*str == '\r') |
174 | break; | |
9d469936 | 175 | if (*str < ' ') { |
31b8bf0a | 176 | continue; |
9d469936 | 177 | } |
31b8bf0a JB |
178 | if (str++ >= smax) |
179 | goto msgerr; | |
180 | } | |
181 | *str = '\0'; | |
182 | (void) alarm(0); | |
183 | return SUCCESS; | |
184 | msgerr: | |
185 | (void) alarm(0); | |
186 | return FAIL; | |
187 | } | |
188 | ||
189 | fwrdata(fp1, fn) | |
190 | FILE *fp1; | |
191 | int fn; | |
192 | { | |
9d469936 | 193 | register int alen, ret; |
9d469936 JB |
194 | char ack, ibuf[MAXMSGLEN]; |
195 | int flen, mil, retries = 0; | |
31b8bf0a JB |
196 | long abytes, fbytes; |
197 | struct timeb t1, t2; | |
0aec7641 | 198 | float ft; |
31b8bf0a JB |
199 | |
200 | ret = FAIL; | |
201 | retry: | |
202 | fchksum = 0xffff; | |
203 | abytes = fbytes = 0L; | |
204 | ack = '\0'; | |
205 | #ifdef USG | |
206 | time(&t1.time); | |
207 | t1.millitm = 0; | |
208 | #else !USG | |
209 | ftime(&t1); | |
210 | #endif !USG | |
9d469936 JB |
211 | do { |
212 | alen = fwrblk(fn, fp1, &flen); | |
31b8bf0a | 213 | fbytes += flen; |
9d469936 JB |
214 | if (alen <= 0) { |
215 | abytes -= alen; | |
216 | goto acct; | |
31b8bf0a | 217 | } |
9d469936 JB |
218 | abytes += alen; |
219 | } while (!feof(fp1) && !ferror(fp1)); | |
220 | DEBUG(8, "\nchecksum: %04x\n", fchksum); | |
221 | if (frdmsg(ibuf, fn) != FAIL) { | |
222 | if ((ack = ibuf[0]) == 'G') | |
223 | ret = SUCCESS; | |
224 | DEBUG(4, "ack - '%c'\n", ack); | |
31b8bf0a JB |
225 | } |
226 | acct: | |
31b8bf0a JB |
227 | #ifdef USG |
228 | time(&t2.time); | |
229 | t2.millitm = 0; | |
230 | #else !USG | |
231 | ftime(&t2); | |
232 | #endif !USG | |
233 | Now = t2; | |
234 | t2.time -= t1.time; | |
235 | mil = t2.millitm - t1.millitm; | |
236 | if (mil < 0) { | |
237 | --t2.time; | |
238 | mil += 1000; | |
239 | } | |
0aec7641 RA |
240 | ft = (float)t2.time + (float)mil/1000.; |
241 | sprintf(ibuf, "sent data %ld bytes %.2f secs %ld bps", | |
242 | fbytes, ft, (long)((float)fbytes*8./ft)); | |
28ed17d4 | 243 | sysacct(abytes, t2.time); |
0aec7641 | 244 | Bytes_Sent += fbytes; |
9d469936 JB |
245 | if (retries > 0) |
246 | sprintf(&ibuf[strlen(ibuf)], ", %d retries", retries); | |
31b8bf0a | 247 | DEBUG(1, "%s\n", ibuf); |
87d365c2 | 248 | log_xferstats(ibuf); |
9d469936 JB |
249 | if (ack == 'R') { |
250 | DEBUG(4, "RETRY:\n", 0); | |
251 | fseek(fp1, 0L, 0); | |
252 | retries++; | |
253 | goto retry; | |
254 | } | |
31b8bf0a | 255 | #ifdef SYSACCT |
9d469936 | 256 | if (ret == FAIL) |
31b8bf0a JB |
257 | sysaccf(NULL); /* force accounting */ |
258 | #endif SYSACCT | |
259 | return ret; | |
260 | } | |
261 | ||
262 | /* max. attempts to retransmit a file: */ | |
263 | #define MAXRETRIES (fbytes < 10000L ? 2 : 1) | |
264 | ||
265 | frddata(fn, fp2) | |
266 | register int fn; | |
267 | register FILE *fp2; | |
268 | { | |
269 | register int flen; | |
270 | register char eof; | |
9d469936 JB |
271 | char ibuf[FIBUFSIZ]; |
272 | int ret, mil, retries = 0; | |
31b8bf0a JB |
273 | long alen, abytes, fbytes; |
274 | struct timeb t1, t2; | |
0aec7641 | 275 | float ft; |
31b8bf0a JB |
276 | |
277 | ret = FAIL; | |
278 | retry: | |
279 | fchksum = 0xffff; | |
280 | abytes = fbytes = 0L; | |
281 | #ifdef USG | |
282 | time(&t1.time); | |
283 | t1.millitm = 0; | |
284 | #else !USG | |
285 | ftime(&t1); | |
286 | #endif !USG | |
287 | do { | |
288 | flen = frdblk(ibuf, fn, &alen); | |
289 | abytes += alen; | |
290 | if (flen < 0) | |
291 | goto acct; | |
9d469936 JB |
292 | if (eof = flen > FIBUFSIZ) |
293 | flen -= FIBUFSIZ + 1; | |
31b8bf0a JB |
294 | fbytes += flen; |
295 | if (fwrite(ibuf, sizeof (char), flen, fp2) != flen) | |
296 | goto acct; | |
297 | } while (!eof); | |
9d469936 | 298 | ret = SUCCESS; |
31b8bf0a | 299 | acct: |
31b8bf0a JB |
300 | #ifdef USG |
301 | time(&t2.time); | |
302 | t2.millitm = 0; | |
303 | #else !USG | |
304 | ftime(&t2); | |
305 | #endif !USG | |
306 | Now = t2; | |
307 | t2.time -= t1.time; | |
308 | mil = t2.millitm - t1.millitm; | |
309 | if (mil < 0) { | |
310 | --t2.time; | |
311 | mil += 1000; | |
312 | } | |
0aec7641 RA |
313 | ft = (float)t2.time + (float)mil/1000.; |
314 | sprintf(ibuf, "received data %ld bytes %.2f secs %ld bps", | |
315 | fbytes, ft, (long)((float)fbytes*8./ft)); | |
31b8bf0a | 316 | if (retries > 0) |
9d469936 | 317 | sprintf(&ibuf[strlen(ibuf)]," %d retries", retries); |
28ed17d4 | 318 | sysacct(abytes, t2.time); |
0aec7641 | 319 | Bytes_Received += fbytes; |
31b8bf0a | 320 | DEBUG(1, "%s\n", ibuf); |
87d365c2 | 321 | log_xferstats(ibuf); |
9d469936 JB |
322 | if (ret == FAIL) { |
323 | if (retries++ < MAXRETRIES) { | |
324 | DEBUG(8, "send ack: 'R'\n", 0); | |
325 | fwrmsg('R', "", fn); | |
326 | fseek(fp2, 0L, 0); | |
327 | DEBUG(4, "RETRY:\n", 0); | |
328 | goto retry; | |
329 | } | |
330 | DEBUG(8, "send ack: 'Q'\n", 0); | |
331 | fwrmsg('Q', "", fn); | |
332 | #ifdef SYSACCT | |
333 | sysaccf(NULL); /* force accounting */ | |
334 | #endif SYSACCT | |
335 | } | |
336 | else { | |
337 | DEBUG(8, "send ack: 'G'\n", 0); | |
338 | fwrmsg('G', "", fn); | |
339 | } | |
31b8bf0a JB |
340 | return ret; |
341 | } | |
342 | ||
343 | static | |
344 | frdbuf(blk, len, fn) | |
345 | register char *blk; | |
346 | register int len; | |
347 | register int fn; | |
348 | { | |
9d469936 | 349 | static int ret = FIBUFSIZ / 2; |
31b8bf0a JB |
350 | |
351 | if (setjmp(Ffailbuf)) | |
352 | return FAIL; | |
31b8bf0a JB |
353 | (void) alarm(MAXMSGTIME); |
354 | ret = read(fn, blk, len); | |
355 | alarm(0); | |
356 | return ret <= 0 ? FAIL : ret; | |
357 | } | |
358 | ||
9d469936 | 359 | #if !defined(BSD4_2) && !defined(USG) |
31b8bf0a | 360 | /* call ultouch every TC calls to either frdblk or fwrblk */ |
9d469936 JB |
361 | #define TC 20 |
362 | static int tc = TC; | |
363 | #endif !defined(BSD4_2) && !defined(USG) | |
31b8bf0a JB |
364 | |
365 | /* Byte conversion: | |
366 | * | |
9d469936 JB |
367 | * from pre to |
368 | * 000-037 172 100-137 | |
369 | * 040-171 040-171 | |
370 | * 172-177 173 072-077 | |
371 | * 200-237 174 100-137 | |
372 | * 240-371 175 040-171 | |
373 | * 372-377 176 072-077 | |
31b8bf0a JB |
374 | */ |
375 | ||
376 | static | |
9d469936 | 377 | fwrblk(fn, fp, lenp) |
31b8bf0a | 378 | int fn; |
9d469936 JB |
379 | register FILE *fp; |
380 | int *lenp; | |
31b8bf0a JB |
381 | { |
382 | register char *op; | |
9d469936 JB |
383 | register int c, sum, nl, len; |
384 | char obuf[FOBUFSIZ + 8]; | |
31b8bf0a | 385 | int ret; |
31b8bf0a | 386 | |
9d469936 | 387 | #if !defined(BSD4_2) && !defined(USG) |
31b8bf0a JB |
388 | /* call ultouch occasionally */ |
389 | if (--tc < 0) { | |
390 | tc = TC; | |
391 | ultouch(); | |
392 | } | |
9d469936 | 393 | #endif !defined(BSD4_2) && !defined(USG) |
31b8bf0a JB |
394 | op = obuf; |
395 | nl = 0; | |
9d469936 | 396 | len = 0; |
31b8bf0a | 397 | sum = fchksum; |
9d469936 JB |
398 | while ((c = getc(fp)) != EOF) { |
399 | len++; | |
31b8bf0a JB |
400 | if (sum & 0x8000) { |
401 | sum <<= 1; | |
402 | sum++; | |
403 | } else | |
404 | sum <<= 1; | |
9d469936 | 405 | sum += c; |
31b8bf0a | 406 | sum &= 0xffff; |
9d469936 JB |
407 | if (c & 0200) { |
408 | c &= 0177; | |
409 | if (c < 040) { | |
31b8bf0a | 410 | *op++ = '\174'; |
9d469936 | 411 | *op++ = c + 0100; |
31b8bf0a | 412 | } else |
9d469936 | 413 | if (c <= 0171) { |
31b8bf0a | 414 | *op++ = '\175'; |
9d469936 | 415 | *op++ = c; |
31b8bf0a JB |
416 | } |
417 | else { | |
418 | *op++ = '\176'; | |
9d469936 | 419 | *op++ = c - 0100; |
31b8bf0a JB |
420 | } |
421 | nl += 2; | |
422 | } else { | |
9d469936 | 423 | if (c < 040) { |
31b8bf0a | 424 | *op++ = '\172'; |
9d469936 | 425 | *op++ = c + 0100; |
31b8bf0a JB |
426 | nl += 2; |
427 | } else | |
9d469936 JB |
428 | if (c <= 0171) { |
429 | *op++ = c; | |
31b8bf0a JB |
430 | nl++; |
431 | } else { | |
432 | *op++ = '\173'; | |
9d469936 | 433 | *op++ = c - 0100; |
31b8bf0a JB |
434 | nl += 2; |
435 | } | |
436 | } | |
9d469936 JB |
437 | if (nl >= FOBUFSIZ - 1) { |
438 | /* | |
439 | * peek at next char, see if it will fit | |
440 | */ | |
441 | c = getc(fp); | |
442 | if (c == EOF) | |
443 | break; | |
444 | (void) ungetc(c, fp); | |
445 | if (nl >= FOBUFSIZ || c < 040 || c > 0171) | |
446 | goto writeit; | |
447 | } | |
448 | } | |
449 | /* | |
450 | * At EOF - append checksum, there is space for it... | |
451 | */ | |
452 | sprintf(op, "\176\176%04x\r", sum); | |
453 | nl += strlen(op); | |
454 | writeit: | |
455 | *lenp = len; | |
31b8bf0a | 456 | fchksum = sum; |
9d469936 | 457 | DEBUG(8, "%d/", len); |
31b8bf0a JB |
458 | DEBUG(8, "%d,", nl); |
459 | ret = write(fn, obuf, nl); | |
460 | return ret == nl ? nl : ret < 0 ? 0 : -ret; | |
461 | } | |
462 | ||
463 | static | |
464 | frdblk(ip, fn, rlen) | |
465 | register char *ip; | |
466 | int fn; | |
467 | long *rlen; | |
468 | { | |
469 | register char *op, c; | |
470 | register int sum, len, nl; | |
471 | char buf[5], *erbp = ip; | |
472 | int i; | |
473 | static char special = 0; | |
474 | ||
9d469936 | 475 | #if !defined(BSD4_2) && !defined(USG) |
31b8bf0a JB |
476 | /* call ultouch occasionally */ |
477 | if (--tc < 0) { | |
478 | tc = TC; | |
479 | ultouch(); | |
480 | } | |
9d469936 JB |
481 | #endif !defined(BSD4_2) && !defined(USG) |
482 | if ((len = frdbuf(ip, FIBUFSIZ, fn)) == FAIL) { | |
31b8bf0a JB |
483 | *rlen = 0; |
484 | goto dcorr; | |
485 | } | |
486 | *rlen = len; | |
487 | DEBUG(8, "%d/", len); | |
488 | op = ip; | |
489 | nl = 0; | |
490 | sum = fchksum; | |
491 | do { | |
492 | if ((*ip &= 0177) >= '\172') { | |
493 | if (special) { | |
494 | DEBUG(8, "%d", nl); | |
495 | special = 0; | |
496 | op = buf; | |
497 | if (*ip++ != '\176' || (i = --len) > 5) | |
498 | goto dcorr; | |
499 | while (i--) | |
9d469936 | 500 | *op++ = *ip++ & 0177; |
31b8bf0a JB |
501 | while (len < 5) { |
502 | i = frdbuf(&buf[len], 5 - len, fn); | |
503 | if (i == FAIL) { | |
504 | len = FAIL; | |
505 | goto dcorr; | |
506 | } | |
507 | DEBUG(8, ",%d", i); | |
508 | len += i; | |
509 | *rlen += i; | |
9d469936 JB |
510 | while (i--) |
511 | *op++ &= 0177; | |
31b8bf0a JB |
512 | } |
513 | if (buf[4] != '\r') | |
514 | goto dcorr; | |
515 | sscanf(buf, "%4x", &fchksum); | |
516 | DEBUG(8, "\nchecksum: %04x\n", sum); | |
517 | if (fchksum == sum) | |
9d469936 | 518 | return FIBUFSIZ + 1 + nl; |
31b8bf0a JB |
519 | else { |
520 | DEBUG(8, "\n", 0); | |
521 | DEBUG(4, "Bad checksum\n", 0); | |
522 | return FAIL; | |
523 | } | |
524 | } | |
525 | special = *ip++; | |
526 | } else { | |
527 | if (*ip < '\040') { | |
528 | /* error: shouldn't get control chars */ | |
529 | goto dcorr; | |
530 | } | |
531 | switch (special) { | |
532 | case 0: | |
533 | c = *ip++; | |
534 | break; | |
535 | case '\172': | |
536 | c = *ip++ - 0100; | |
537 | break; | |
538 | case '\173': | |
539 | c = *ip++ + 0100; | |
540 | break; | |
541 | case '\174': | |
542 | c = *ip++ + 0100; | |
543 | break; | |
544 | case '\175': | |
545 | c = *ip++ + 0200; | |
546 | break; | |
547 | case '\176': | |
548 | c = *ip++ + 0300; | |
549 | break; | |
550 | } | |
551 | *op++ = c; | |
552 | if (sum & 0x8000) { | |
553 | sum <<= 1; | |
554 | sum++; | |
555 | } else | |
556 | sum <<= 1; | |
557 | sum += c & 0377; | |
558 | sum &= 0xffff; | |
559 | special = 0; | |
560 | nl++; | |
561 | } | |
562 | } while (--len); | |
563 | fchksum = sum; | |
564 | DEBUG(8, "%d,", nl); | |
565 | return nl; | |
566 | dcorr: | |
567 | DEBUG(8, "\n", 0); | |
568 | DEBUG(4, "Data corrupted\n", 0); | |
569 | while (len != FAIL) { | |
9d469936 | 570 | if ((len = frdbuf(erbp, FIBUFSIZ, fn)) != FAIL) |
31b8bf0a JB |
571 | *rlen += len; |
572 | } | |
573 | return FAIL; | |
574 | } | |
9d469936 | 575 |