]> git.saurik.com Git - apple/security.git/blob - SecurityTests/cspxutils/hashTime/hashTime.cpp
d928a44acabc01b1b26cc6d7423959baaebc9bc3
[apple/security.git] / SecurityTests / cspxutils / hashTime / hashTime.cpp
1 /*
2 * hashTime.cpp - measure performance of digest ops
3 */
4
5 #include <Security/Security.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include "cputime.h"
10 #include "cspwrap.h"
11 #include "common.h"
12 #include <openssl/md5.h>
13 #include <openssl/sha.h>
14 #include <CommonCrypto/CommonDigest.h>
15 #include "MD5.h" /* CryptKit version used Panther and prior */
16 #include "SHA1.h" /* ditto */
17
18 /* enumerate digest algorithms our way */
19 typedef int HT_Alg;
20 enum {
21 HA_MD5 = 0,
22 HA_SHA1,
23 HA_SHA224,
24 HA_SHA256,
25 HA_SHA384,
26 HA_SHA512
27 };
28
29 #define FIRST_ALG HA_MD5
30 #define LAST_ALG HA_SHA512
31
32 static void usage(char **argv)
33 {
34 printf("Usage: %s [option ...]\n", argv[0]);
35 printf("Options:\n");
36 printf(" t=testspec; default=all\n");
37 printf(" test specs: c : digest context setup/teardown\n");
38 printf(" b : basic single block digest\n");
39 printf(" d : digest lots of data\n");
40 printf(" a=alg; default=all\n");
41 printf(" algs: m : MD5\n");
42 printf(" s : SHA1\n");
43 printf(" 4 : SHA224\n");
44 printf(" 2 : SHA256\n");
45 printf(" 3 : SHA384\n");
46 printf(" 5 : SHA512\n");
47 printf(" l=loops (only valid if testspec is given)\n");
48 printf(" o (use openssl implementations, MD5 and SHA1 only)\n");
49 printf(" c (use CommonCrypto implementation)\n");
50 printf(" k (use CryptKit implementations, MD5 and SHA1 only\n");
51 printf(" v verify digest by printing it\n");
52 exit(1);
53 }
54
55 static void dumpDigest(
56 const unsigned char *digest,
57 unsigned len)
58 {
59 for(unsigned dex=0; dex<len; dex++) {
60 printf("%02X", *digest++);
61 if((dex % 4) == 3) {
62 printf(" ");
63 }
64 }
65 printf("\n");
66 }
67
68 /* sort-of random, but repeatable */
69 static void initPtext(
70 unsigned char *ptext,
71 unsigned len)
72 {
73 srandom(1);
74 for(unsigned dex=0; dex<len; dex++) {
75 *ptext++ = random();
76 }
77 }
78
79 /* passed to each test */
80 typedef struct {
81 unsigned loops;
82 CSSM_CSP_HANDLE cspHand;
83 CSSM_ALGORITHMS algId; // MD5, SHA1
84 bool dumpDigest;
85 } TestParams;
86
87 /* just CDSA context setup/teardown - no CSP activity */
88 static CSSM_RETURN hashContext(
89 TestParams *params)
90 {
91 CSSM_CC_HANDLE ccHand;
92 CSSM_RETURN crtn;
93 unsigned loop;
94 CPUTime startTime;
95 double timeSpentMs;
96
97 startTime = CPUTimeRead();
98 for(loop=0; loop<params->loops; loop++) {
99 crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
100 params->algId, &ccHand);
101 if(crtn) {
102 return crtn;
103 }
104 crtn = CSSM_DeleteContext(ccHand);
105 if(crtn) {
106 return crtn;
107 }
108 }
109 timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
110 printf(" context setup/delete : %u ops in %.2f ms; %f ms/op\n",
111 params->loops, timeSpentMs, timeSpentMs / (double)params->loops);
112 return CSSM_OK;
113 }
114
115 /* Minimal CSP init/digest/final */
116 #define BASIC_BLOCK_SIZE 64 // to digest in bytes
117 #define MAX_DIGEST_SIZE 64 // we provide, no malloc below CSSM
118
119 static CSSM_RETURN hashBasic(
120 TestParams *params)
121 {
122 CSSM_CC_HANDLE ccHand;
123 CSSM_RETURN crtn;
124 unsigned loop;
125 CPUTime startTime;
126 double timeSpentMs;
127 uint8 ptext[BASIC_BLOCK_SIZE];
128 uint8 digest[MAX_DIGEST_SIZE];
129 CSSM_DATA ptextData = {BASIC_BLOCK_SIZE, ptext};
130 CSSM_DATA digestData = {MAX_DIGEST_SIZE, digest};
131
132 /* we reuse this one inside the loop */
133 crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
134 params->algId, &ccHand);
135 if(crtn) {
136 return crtn;
137 }
138
139 /* random data, const thru the loops */
140 appGetRandomBytes(ptext, BASIC_BLOCK_SIZE);
141
142 /* start critical timing loop */
143 startTime = CPUTimeRead();
144 for(loop=0; loop<params->loops; loop++) {
145 crtn = CSSM_DigestDataInit(ccHand);
146 if(crtn) {
147 return crtn;
148 }
149 crtn = CSSM_DigestDataUpdate(ccHand, &ptextData, 1);
150 if(crtn) {
151 return crtn;
152 }
153 crtn = CSSM_DigestDataFinal(ccHand, &digestData);
154 if(crtn) {
155 return crtn;
156 }
157 }
158 CSSM_DeleteContext(ccHand);
159 timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
160 printf(" Digest one %u byte block : %u ops in %.2f ms; %f ms/op\n",
161 BASIC_BLOCK_SIZE, params->loops,
162 timeSpentMs, timeSpentMs / (double)params->loops);
163 return CSSM_OK;
164 }
165
166 /* Lots of data */
167 #define PTEXT_SIZE 1000 // to digest in bytes
168 #define INNER_LOOPS 1000
169
170 static CSSM_RETURN hashDataRate(
171 TestParams *params)
172 {
173 CSSM_CC_HANDLE ccHand;
174 CSSM_RETURN crtn;
175 unsigned loop;
176 unsigned iloop;
177 CPUTime startTime;
178 double timeSpent, timeSpentMs;
179 uint8 ptext[PTEXT_SIZE];
180 uint8 digest[MAX_DIGEST_SIZE];
181 CSSM_DATA ptextData = {PTEXT_SIZE, ptext};
182 CSSM_DATA digestData = {MAX_DIGEST_SIZE, digest};
183
184 /* we reuse this one inside the loop */
185 crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
186 params->algId, &ccHand);
187 if(crtn) {
188 return crtn;
189 }
190
191 /* random data, const thru the loops */
192 initPtext(ptext, PTEXT_SIZE);
193
194 /* start critical timing loop */
195 startTime = CPUTimeRead();
196 for(loop=0; loop<params->loops; loop++) {
197 crtn = CSSM_DigestDataInit(ccHand);
198 if(crtn) {
199 return crtn;
200 }
201 for(iloop=0; iloop<INNER_LOOPS; iloop++) {
202 crtn = CSSM_DigestDataUpdate(ccHand, &ptextData, 1);
203 if(crtn) {
204 return crtn;
205 }
206 }
207 crtn = CSSM_DigestDataFinal(ccHand, &digestData);
208 if(crtn) {
209 return crtn;
210 }
211 }
212 timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
213 timeSpent = timeSpentMs / 1000.0;
214
215 CSSM_DeleteContext(ccHand);
216 float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
217 float totalBytes = params->loops * bytesPerLoop;
218
219 /* careful, KByte = 1024, ms = 1/1000 */
220 printf(" Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
221 bytesPerLoop, params->loops,
222 timeSpentMs, timeSpentMs / (double)params->loops,
223 ((float)totalBytes / 1024.0) / timeSpent);
224 if(params->dumpDigest) {
225 dumpDigest(digest, digestData.Length);
226 }
227 return CSSM_OK;
228 }
229
230 /* Lots of data, openssl version */
231
232 typedef union {
233 MD5_CTX md5;
234 SHA_CTX sha;
235 } OS_CTX;
236
237 typedef void (*initFcn)(void *digestCtx);
238 typedef void (*updateFcn)(void *digestCtx, const void *data, unsigned long len);
239 typedef void (*finalFcn)(unsigned char *digest, void *digestCtx);
240
241 static CSSM_RETURN hashDataRateOpenssl(
242 TestParams *params)
243 {
244 OS_CTX ctx;
245 initFcn initPtr = NULL;
246 updateFcn updatePtr = NULL;
247 finalFcn finalPtr = NULL;
248 unsigned loop;
249 unsigned iloop;
250 CPUTime startTime;
251 double timeSpent, timeSpentMs;
252 uint8 ptext[PTEXT_SIZE];
253 uint8 digest[MAX_DIGEST_SIZE];
254 unsigned digestLen = 16;
255
256 /* we reuse this one inside the loop */
257 switch(params->algId) {
258 case CSSM_ALGID_SHA1:
259 initPtr = (initFcn)SHA1_Init;
260 updatePtr = (updateFcn)SHA1_Update;
261 finalPtr = (finalFcn)SHA1_Final;
262 digestLen = 20;
263 break;
264 case CSSM_ALGID_MD5:
265 initPtr = (initFcn)MD5_Init;
266 updatePtr = (updateFcn)MD5_Update;
267 finalPtr = (finalFcn)MD5_Final;
268 break;
269 default:
270 printf("***Sorry, Openssl can only do SHA1 and MD5.\n");
271 return 1;
272 }
273
274 /* random data, const thru the loops */
275 initPtext(ptext, PTEXT_SIZE);
276
277 /* start critical timing loop */
278 startTime = CPUTimeRead();
279 for(loop=0; loop<params->loops; loop++) {
280 initPtr(&ctx);
281 for(iloop=0; iloop<INNER_LOOPS; iloop++) {
282 updatePtr(&ctx, ptext, PTEXT_SIZE);
283 }
284 finalPtr(digest, &ctx);
285 }
286 timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
287 timeSpent = timeSpentMs / 1000.0;
288
289 float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
290 float totalBytes = params->loops * bytesPerLoop;
291
292 /* careful, KByte = 1024, ms = 1/1000 */
293 printf(" Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
294 bytesPerLoop, params->loops,
295 timeSpentMs, timeSpentMs / (double)params->loops,
296 ((float)totalBytes / 1024.0) / timeSpent);
297 if(params->dumpDigest) {
298 dumpDigest(digest, digestLen);
299 }
300 return CSSM_OK;
301 }
302
303 /* Lots of data, CommonCrypto version (not thru CSP) */
304
305 typedef union {
306 CC_MD5_CTX md5;
307 CC_SHA1_CTX sha;
308 CC_SHA256_CTX sha256;
309 CC_SHA512_CTX sha512;
310 } CC_CTX;
311
312 typedef void (*ccUpdateFcn)(void *digestCtx, const void *data, CC_LONG len);
313 typedef void (*ccFinalFcn)(unsigned char *digest, void *digestCtx);
314
315 static CSSM_RETURN hashDataRateCommonCrypto(
316 TestParams *params)
317 {
318 CC_CTX ctx;
319 ccUpdateFcn updatePtr = NULL;
320 ccFinalFcn finalPtr = NULL;
321 initFcn initPtr = NULL;
322 unsigned loop;
323 unsigned iloop;
324 CPUTime startTime;
325 double timeSpent, timeSpentMs;
326 uint8 ptext[PTEXT_SIZE];
327 uint8 digest[MAX_DIGEST_SIZE];
328 unsigned digestLen = 16;
329
330 /* we reuse this one inside the loop */
331 switch(params->algId) {
332 case CSSM_ALGID_SHA1:
333 initPtr = (initFcn)CC_SHA1_Init;
334 updatePtr = (ccUpdateFcn)CC_SHA1_Update;
335 finalPtr = (ccFinalFcn)CC_SHA1_Final;
336 digestLen = CC_SHA1_DIGEST_LENGTH;
337 break;
338 case CSSM_ALGID_SHA224:
339 initPtr = (initFcn)CC_SHA224_Init;
340 updatePtr = (ccUpdateFcn)CC_SHA224_Update;
341 finalPtr = (ccFinalFcn)CC_SHA224_Final;
342 digestLen = CC_SHA224_DIGEST_LENGTH;
343 break;
344 case CSSM_ALGID_SHA256:
345 initPtr = (initFcn)CC_SHA256_Init;
346 updatePtr = (ccUpdateFcn)CC_SHA256_Update;
347 finalPtr = (ccFinalFcn)CC_SHA256_Final;
348 digestLen = CC_SHA256_DIGEST_LENGTH;
349 break;
350 case CSSM_ALGID_SHA384:
351 initPtr = (initFcn)CC_SHA384_Init;
352 updatePtr = (ccUpdateFcn)CC_SHA384_Update;
353 finalPtr = (ccFinalFcn)CC_SHA384_Final;
354 digestLen = CC_SHA384_DIGEST_LENGTH;
355 break;
356 case CSSM_ALGID_SHA512:
357 initPtr = (initFcn)CC_SHA512_Init;
358 updatePtr = (ccUpdateFcn)CC_SHA512_Update;
359 finalPtr = (ccFinalFcn)CC_SHA512_Final;
360 digestLen = CC_SHA512_DIGEST_LENGTH;
361 break;
362 case CSSM_ALGID_MD5:
363 initPtr = (initFcn)CC_MD5_Init;
364 updatePtr = (ccUpdateFcn)CC_MD5_Update;
365 finalPtr = (ccFinalFcn)CC_MD5_Final;
366 digestLen = CC_MD5_DIGEST_LENGTH;
367 break;
368 default:
369 printf("***BRRRZAP!\n");
370 return 1;
371 }
372
373 /* random data, const thru the loops */
374 initPtext(ptext, PTEXT_SIZE);
375
376 /* start critical timing loop */
377 startTime = CPUTimeRead();
378 for(loop=0; loop<params->loops; loop++) {
379 initPtr(&ctx);
380 for(iloop=0; iloop<INNER_LOOPS; iloop++) {
381 updatePtr(&ctx, ptext, PTEXT_SIZE);
382 }
383 finalPtr(digest, &ctx);
384 }
385 timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
386 timeSpent = timeSpentMs / 1000.0;
387
388 float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
389 float totalBytes = params->loops * bytesPerLoop;
390
391 /* careful, KByte = 1024, ms = 1/1000 */
392 printf(" Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
393 bytesPerLoop, params->loops,
394 timeSpentMs, timeSpentMs / (double)params->loops,
395 ((float)totalBytes / 1024.0) / timeSpent);
396 if(params->dumpDigest) {
397 dumpDigest(digest, digestLen);
398 }
399 return CSSM_OK;
400 }
401
402 /* Lots of data, CryptKit version */
403
404 /* cryptkit final routines are not orthoganal, fix up here */
405 static void ckSha1Final(
406 unsigned char *digest,
407 void *ctx)
408 {
409 sha1GetDigest((sha1Obj)ctx, digest);
410 }
411
412 static void ckMD5Final(
413 unsigned char *digest,
414 void *ctx)
415 {
416 MD5Final((struct MD5Context *)ctx, digest);
417 }
418
419 typedef void (*ckUpdateFcn)(void *digestCtx, const void *data, unsigned len);
420 typedef void (*ckFinalFcn)(unsigned char *digest, void *digestCtx);
421
422 static CSSM_RETURN hashDataRateCryptKit(
423 TestParams *params)
424 {
425 ckUpdateFcn updatePtr = NULL;
426 ckFinalFcn finalPtr = NULL;
427 initFcn initPtr = NULL;
428 struct MD5Context md5;
429 sha1Obj sha;
430 void *ctx;
431
432 unsigned loop;
433 unsigned iloop;
434 CPUTime startTime;
435 double timeSpent, timeSpentMs;
436 uint8 ptext[PTEXT_SIZE];
437 uint8 digest[MAX_DIGEST_SIZE];
438 unsigned digestLen = 16;
439
440 /* we reuse this one inside the loop */
441 switch(params->algId) {
442 case CSSM_ALGID_SHA1:
443 sha = sha1Alloc();
444 ctx = sha;
445 initPtr = (initFcn)sha1Reinit;
446 updatePtr = (ckUpdateFcn)sha1AddData;
447 finalPtr = (ckFinalFcn)ckSha1Final;
448 digestLen = 20;
449 break;
450 case CSSM_ALGID_MD5:
451 ctx = &md5;
452 initPtr = (initFcn)MD5Init;
453 updatePtr = (ckUpdateFcn)MD5Update;
454 finalPtr = (ckFinalFcn)ckMD5Final;
455 break;
456 default:
457 printf("***Sorry, CryptKit can only do SHA1 and MD5.\n");
458 return 1;
459 }
460
461 /* random data, const thru the loops */
462 initPtext(ptext, PTEXT_SIZE);
463
464 /* start critical timing loop */
465 startTime = CPUTimeRead();
466 for(loop=0; loop<params->loops; loop++) {
467 initPtr(ctx);
468 for(iloop=0; iloop<INNER_LOOPS; iloop++) {
469 updatePtr(ctx, ptext, PTEXT_SIZE);
470 }
471 finalPtr(digest, ctx);
472 }
473 timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
474 timeSpent = timeSpentMs / 1000.0;
475
476 float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
477 float totalBytes = params->loops * bytesPerLoop;
478
479 /* careful, KByte = 1024, ms = 1/1000 */
480 printf(" Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
481 bytesPerLoop, params->loops,
482 timeSpentMs, timeSpentMs / (double)params->loops,
483 ((float)totalBytes / 1024.0) / timeSpent);
484 if(params->dumpDigest) {
485 dumpDigest(digest, digestLen);
486 }
487 return CSSM_OK;
488 }
489
490 typedef CSSM_RETURN (*testRunFcn)(TestParams *testParams);
491
492 /*
493 * Static declaration of a test
494 */
495 typedef struct {
496 const char *testName;
497 unsigned loops;
498 testRunFcn run;
499 char testSpec; // for t=xxx cmd line opt
500 } TestDefs;
501
502 static TestDefs testDefs[] =
503 {
504 { "Digest context setup/teardown",
505 100000,
506 hashContext,
507 'c',
508 },
509 { "Basic single block digest",
510 100000,
511 hashBasic,
512 'b',
513 },
514 { "Large data digest",
515 1000,
516 hashDataRate,
517 'd',
518 },
519 };
520
521 static TestDefs testDefsOpenSSL[] =
522 {
523 { "Digest context setup/teardown",
524 100000,
525 NULL, // not implemented
526 'c',
527 },
528 { "Basic single block digest",
529 100000,
530 NULL, // not implemented
531 'b',
532 },
533 { "Large data digest, OpenSSL",
534 1000,
535 hashDataRateOpenssl,
536 'd',
537 },
538 };
539
540 static TestDefs testDefsCommonCrypto[] =
541 {
542 { "Digest context setup/teardown",
543 100000,
544 NULL, // not implemented
545 'c',
546 },
547 { "Basic single block digest",
548 100000,
549 NULL, // not implemented
550 'b',
551 },
552 { "Large data digest, CommonCrypto",
553 1000,
554 hashDataRateCommonCrypto,
555 'd',
556 },
557 };
558
559 static TestDefs testDefsCryptKit[] =
560 {
561 { "Digest context setup/teardown",
562 100000,
563 NULL, // not implemented
564 'c',
565 },
566 { "Basic single block digest",
567 100000,
568 NULL, // not implemented
569 'b',
570 },
571 { "Large data digest, CryptKit",
572 1000,
573 hashDataRateCryptKit,
574 'd',
575 },
576 };
577
578
579 static void algToAlgId(
580 HT_Alg alg,
581 CSSM_ALGORITHMS *algId,
582 const char **algStr)
583 {
584 switch(alg) {
585 case HA_MD5:
586 *algId = CSSM_ALGID_MD5;
587 *algStr = "MD5";
588 break;
589 case HA_SHA1:
590 *algId = CSSM_ALGID_SHA1;
591 *algStr = "SHA1";
592 break;
593 case HA_SHA224:
594 *algId = CSSM_ALGID_SHA224;
595 *algStr = "SHA224";
596 break;
597 case HA_SHA256:
598 *algId = CSSM_ALGID_SHA256;
599 *algStr = "SHA256";
600 break;
601 case HA_SHA384:
602 *algId = CSSM_ALGID_SHA384;
603 *algStr = "SHA384";
604 break;
605 case HA_SHA512:
606 *algId = CSSM_ALGID_SHA512;
607 *algStr = "SHA512";
608 break;
609 default:
610 printf("***algToAlgId screwup\n");
611 exit(1);
612 }
613 }
614
615 #define NUM_TESTS (sizeof(testDefs) / sizeof(testDefs[0]))
616
617 int main(int argc, char **argv)
618 {
619 TestParams testParams;
620 TestDefs *testDef;
621 TestDefs *ourTestDefs = testDefs;
622 CSSM_RETURN crtn;
623 int arg;
624 char *argp;
625 unsigned cmdLoops = 0; // can be specified in cmd line
626 // if not, use TestDefs.loops
627 char testSpec = '\0'; // allows specification of one test
628 // otherwise run all
629 HT_Alg alg;
630 const char *algStr;
631 int firstAlg = FIRST_ALG;
632 int lastAlg = LAST_ALG;
633
634 memset(&testParams, 0, sizeof(testParams));
635
636 for(arg=1; arg<argc; arg++) {
637 argp = argv[arg];
638 switch(argp[0]) {
639 case 't':
640 testSpec = argp[2];
641 break;
642 case 'l':
643 cmdLoops = atoi(&argp[2]);
644 break;
645 case 'a':
646 if(argp[1] == '\0') {
647 usage(argv);
648 }
649 switch(argp[2]) {
650 case 'm':
651 firstAlg = lastAlg = HA_MD5;
652 break;
653 case 's':
654 firstAlg = lastAlg = HA_SHA1;
655 break;
656 case '4':
657 firstAlg = lastAlg = HA_SHA224;
658 break;
659 case '2':
660 firstAlg = lastAlg = HA_SHA256;
661 break;
662 case '3':
663 firstAlg = lastAlg = HA_SHA384;
664 break;
665 case '5':
666 firstAlg = lastAlg = HA_SHA512;
667 break;
668 default:
669 usage(argv);
670 }
671 break;
672 case 'o':
673 ourTestDefs = testDefsOpenSSL;
674 break;
675 case 'c':
676 ourTestDefs = testDefsCommonCrypto;
677 break;
678 case 'k':
679 ourTestDefs = testDefsCryptKit;
680 break;
681 case 'v':
682 testParams.dumpDigest = true;
683 break;
684 default:
685 usage(argv);
686 }
687 }
688
689 testParams.cspHand = cspStartup();
690 if(testParams.cspHand == 0) {
691 printf("***Error attaching to CSP. Aborting.\n");
692 exit(1);
693 }
694
695 for(unsigned testNum=0; testNum<NUM_TESTS; testNum++) {
696 testDef = &ourTestDefs[testNum];
697
698 if(testSpec && (testDef->testSpec != testSpec)) {
699 continue;
700 }
701 if(testDef->run == NULL) {
702 continue;
703 }
704 printf("%s:\n", testDef->testName);
705 if(cmdLoops) {
706 /* user specified */
707 testParams.loops = cmdLoops;
708 }
709 else {
710 /* default */
711 testParams.loops = testDef->loops;
712 }
713 for(alg=firstAlg; alg<=lastAlg; alg++) {
714 algToAlgId(alg, &testParams.algId, &algStr);
715 printf(" === %s ===\n", algStr);
716 crtn = testDef->run(&testParams);
717 if(crtn) {
718 exit(1);
719 }
720 }
721 }
722 return 0;
723 }