This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
NetWare update from Ananth Kesari.
[perl5.git] / NetWare / NWUtil.c
CommitLineData
2986a63f
JH
1
2/*
3 * Copyright © 2001 Novell, Inc. All Rights Reserved.
4 *
5 * You may distribute under the terms of either the GNU General Public
6 * License or the Artistic License, as specified in the README file.
7 *
8 */
9
10/*
11 * FILENAME : NWUtil.c
12 * DESCRIPTION : Utility functions for NetWare implementation of Perl.
13 * Author : HYAK
14 * Date : Januray 2001.
15 *
16 */
17
18
19
20#include "stdio.h"
21#include "string.h"
22
23#include <nwdsdefs.h> // For "MAX_DN_BYTES"
24#include <malloc.h> // For "malloc" and "free"
25#include <stdlib.h> // For "getenv"
26#include <ctype.h> // For "isspace"
27
28#include <process.h>
29#include <unistd.h>
30#include <errno.h>
31#include <nwerrno.h>
32
33#include <nwlocale.h>
34#include <nwadv.h>
35
36#include "nwutil.h"
37
38
39#define TRUE 1
40#define FALSE 0
41
42
43/**
44 Global variables used for better token parsing. When these were absent,
45 token parsing was not correct when there were more number of arguments passed.
46 These are used in fnCommandLineParser, fnSkipToken and fnScanToken to get/return
47 the correct and updated pointer to the command line string.
48**/
49char *s1 = NULL; // Used in fnScanToken.
50char *s2 = NULL; // Used in fnSkipToken.
51
52
53
54
55/*============================================================================================
56
57 Function : fnSkipWhite
58
59 Description : This function skips the white space characters in the given string and
60 returns the resultant value.
61
62 Parameters : s (IN) - Input string.
63
64 Returns : String.
65
66==============================================================================================*/
67
68char *fnSkipWhite(char *s)
69{
70 while (isspace(*s))
71 s++;
72 return s;
73}
74
75
76
77/*============================================================================================
78
79 Function : fnNwGetEnvironmentStr
80
81 Description : This function returns the NetWare environment string if available,
82 otherwise returns the supplied default value
83
84 Parameters : name (IN) - To hold the NetWare environment value.
85 defaultvalue (IN) - Default value.
86
87
88 Returns : String.
89
90==============================================================================================*/
91
92char *fnNwGetEnvironmentStr(char *name, char *defaultvalue)
93{
94 char* ret = getenv(name);
95 if (ret == NULL)
96 ret = defaultvalue;
97 return ret;
98}
99
100
101
102/*============================================================================================
103
104 Function : fnCommandLineParser
105
106 Description : This function parses the command line into argc/argv style of
107 Number of params and array of params.
108
109 Parameters : pclp (IN) - CommandLine structure.
110 commandLine (IN) - CommandLine String.
111 preserverQuotes (IN) - Indicates whether to preserve/copy the quotes or not.
112
113 Returns : Nothing.
114
115==============================================================================================*/
116
117void fnCommandLineParser(PCOMMANDLINEPARSER pclp, char * commandLine, BOOL preserveQuotes)
118{
119 char *buffer = NULL;
120
121 int index = 0;
122 int do_delete = 1;
123 int i=0, j=0, k=0;
124
125
126 // +1 makes room for the terminating NULL
127 buffer = (char *) malloc((strlen(commandLine) + 1) * sizeof(char));
128 if (buffer == NULL)
129 {
130 pclp->m_isValid = FALSE;
131 return;
132 }
133
134 if (preserveQuotes)
135 {
136 // No I/O redirection nor quote processing if preserveQuotes
137
138 char *s = NULL;
139 char *sSkippedToken = NULL;
140
141
142 strcpy(buffer, commandLine);
143 s = buffer;
144 s = fnSkipWhite(s); // Skip white spaces.
145
146 s2 = s; // Update the global pointer.
147
148
149 pclp->sSkippedToken = (char *) malloc(MAX_DN_BYTES * sizeof(char));
150 if(pclp->sSkippedToken == NULL)
151 {
152 pclp->m_isValid = FALSE;
153 return;
154 }
155
156 while (*s && pclp->m_isValid)
157 {
158/****
159// Commented since only one time malloc and free is enough as is done outside this while loop.
160// It is not required to do them everytime the execution comes into this while loop.
161// Still retained here. Remove this once things are proved to be working fine to a good confident level,
162
163 if(pclp->sSkippedToken)
164 {
165 free(pclp->sSkippedToken);
166 pclp->sSkippedToken = NULL;
167 }
168
169 if(pclp->sSkippedToken == NULL)
170 {
171 pclp->sSkippedToken = (char *) malloc(MAX_DN_BYTES * sizeof(char));
172 if(pclp->sSkippedToken == NULL)
173 {
174 pclp->m_isValid = FALSE;
175 return;
176 }
177 }
178****/
179
180 // Empty the string.
181 strncpy(pclp->sSkippedToken, "", (MAX_DN_BYTES * sizeof(char)));
182
183 // s is advanced by fnSkipToken
184 pclp->sSkippedToken = fnSkipToken(s, pclp->sSkippedToken); // Collect the next command-line argument.
185
186 s2 = fnSkipWhite(s2); // s2 is already updated by fnSkipToken.
187 s = s2; // Update the local pointer too.
188
189 fnAppendArgument(pclp, pclp->sSkippedToken); // Append the argument into an array.
190 }
191
192 if(pclp->sSkippedToken)
193 {
194 free(pclp->sSkippedToken);
195 pclp->sSkippedToken = NULL;
196 }
197 }
198 else
199 {
200 char *s = NULL;
201
202 strcpy(buffer, commandLine);
203 s = buffer;
204 s = fnSkipWhite(s);
205
206 s1 = s; // Update the global pointer.
207
208 while (*s && pclp->m_isValid)
209 {
210 // s is advanced by fnScanToken
211 // Check for I/O redirection here, *outside* of
212 // fnScanToken(), so that quote-protected angle
213 // brackets do NOT cause redirection.
214 if (*s == '<')
215 {
216 s = fnSkipWhite(s+1); // get stdin redirection
217
218 if(pclp->m_redirInName)
219 {
220 free(pclp->m_redirInName);
221 pclp->m_redirInName = NULL;
222 }
223
224 if(pclp->m_redirInName == NULL)
225 {
226 pclp->m_redirInName = (char *) malloc(MAX_DN_BYTES * sizeof(char));
227 if(pclp->m_redirInName == NULL)
228 {
229 pclp->m_isValid = FALSE;
230 return;
231 }
232 }
233
234 // Collect the next command-line argument.
235 pclp->m_redirInName = fnScanToken(s, pclp->m_redirInName);
236
237 s1 = fnSkipWhite(s1); // s1 is already updated by fnScanToken.
238 s = s1; // Update the local pointer too.
239 }
240 else if (*s == '>')
241 {
242 s = fnSkipWhite(s+1); //get stdout redirection
243
244 if(pclp->m_redirOutName)
245 {
246 free(pclp->m_redirOutName);
247 pclp->m_redirOutName = NULL;
248 }
249
250 if(pclp->m_redirOutName == NULL)
251 {
252 pclp->m_redirOutName = (char *) malloc(MAX_DN_BYTES * sizeof(char));
253 if(pclp->m_redirOutName == NULL)
254 {
255 pclp->m_isValid = FALSE;
256 return;
257 }
258 }
259
260 // Collect the next command-line argument.
261 pclp->m_redirOutName = fnScanToken(s, pclp->m_redirOutName);
262
263 s1 = fnSkipWhite(s1); // s1 is already updated by fnScanToken.
264 s = s1; // Update the local pointer too.
265 }
266 else if (*s == '2' && s[1] == '>')
267 {
268 s = fnSkipWhite(s+2); // get stderr redirection
269
270 if(pclp->m_redirErrName)
271 {
272 free(pclp->m_redirErrName);
273 pclp->m_redirErrName = NULL;
274 }
275
276 if(pclp->m_redirErrName == NULL)
277 {
278 pclp->m_redirErrName = (char *) malloc(MAX_DN_BYTES * sizeof(char));
279 if(pclp->m_redirErrName == NULL)
280 {
281 pclp->m_isValid = FALSE;
282 return;
283 }
284 }
285
286 // Collect the next command-line argument.
287 pclp->m_redirErrName = fnScanToken(s, pclp->m_redirErrName);
288
289 s1 = fnSkipWhite(s1); // s1 is already updated by fnScanToken.
290 s = s1; // Update the local pointer too.
291 }
292 else if (*s == '&' && s[1] == '>')
293 {
294 s = fnSkipWhite(s+2); // get stdout+stderr redirection
295
296 if(pclp->m_redirBothName)
297 {
298 free(pclp->m_redirBothName);
299 pclp->m_redirBothName = NULL;
300 }
301
302 if(pclp->m_redirBothName == NULL)
303 {
304 pclp->m_redirBothName = (char *) malloc(MAX_DN_BYTES * sizeof(char));
305 if(pclp->m_redirBothName == NULL)
306 {
307 pclp->m_isValid = FALSE;
308 return;
309 }
310 }
311
312 // Collect the next command-line argument.
313 pclp->m_redirBothName = fnScanToken(s, pclp->m_redirBothName);
314
315 s1 = fnSkipWhite(s1); // s1 is already updated by fnScanToken.
316 s = s1; // Update the local pointer too.
317 }
318 else
319 {
320 if(pclp->nextarg)
321 {
322 free(pclp->nextarg);
323 pclp->nextarg = NULL;
324 }
325
326 if(pclp->nextarg == NULL)
327 {
328 pclp->nextarg = (char *) malloc(MAX_DN_BYTES * sizeof(char));
329 if(pclp->nextarg == NULL)
330 {
331 pclp->m_isValid = FALSE;
332 return;
333 }
334 }
335
336 // Collect the next command-line argument.
337 pclp->nextarg = fnScanToken(s, pclp->nextarg);
338
339 s1 = fnSkipWhite(s1); // s1 is already updated by fnScanToken.
340 s = s1; // Update the local pointer too.
341
342 // Append the next command-line argument into an array.
343 fnAppendArgument(pclp, pclp->nextarg);
344 }
345 }
346 }
347
348
349 // The -{ option, the --noscreen option, the --autodestroy option, if present,
350 // are processed now and removed from the argument vector.
351 for(index=0; index < pclp->m_argc; )
352 {
353 // "-q" is replaced by "-{", because of clash with GetOpt - sgp - 7th Nov 2000
354 // Copied from NDK build - Jan 5th 2001
355 if (strncmp(pclp->m_argv[index], (char *)"-{", 2) == 0)
356 {
357 // found a -q option; grab the semaphore number
358 sscanf(pclp->m_argv[index], (char *)"-{%x", &pclp->m_qSemaphore);
359 fnDeleteArgument(pclp, index); // Delete the argument from the list.
360 }
361 else if (strcmp(pclp->m_argv[index], (char *)"--noscreen") == 0)
362 {
363 // found a --noscreen option
364 pclp->m_noScreen = 1;
365 fnDeleteArgument(pclp, index);
366 }
367 else if (strcmp(pclp->m_argv[index], (char *)"--autodestroy") == 0)
368 {
369 // found a --autodestroy option - create a screen but close automatically
370 pclp->m_AutoDestroy = 1;
371 fnDeleteArgument(pclp, index);
372 }
373 else
374 index++;
375 }
376
377 // pclp->m_isValid is TRUE if there are more than 2 command line parameters OR
378 // if there is only one command and if it is the comman PERL.
379 pclp->m_isValid = ((pclp->m_argc >= 2) || ((pclp->m_argc > 0) && (stricmp(pclp->m_argv[0], LOAD_COMMAND) != 0)));
380
381 if(buffer)
382 {
383 free(buffer);
384 buffer = NULL;
385 }
386
387 return;
388}
389
390
391
392/*============================================================================================
393
394 Function : fnAppendArgument
395
396 Description : This function appends the arguments into a list.
397
398 Parameters : pclp (IN) - CommandLine structure.
399 new_arg (IN) - The new argument to be appended.
400
401 Returns : Nothing.
402
403==============================================================================================*/
404
405void fnAppendArgument(PCOMMANDLINEPARSER pclp, char *new_arg)
406{
407 char **new_argv = pclp->new_argv;
408
409 int new_argv_len = pclp->m_argv_len*2;
410 int i = 0, j = 0;
411
412
413 // Lengthen the argument vector if there's not room for another.
414 // Testing for 'm_argc+2' rather than 'm_argc+1' in the test guarantees
415 // that there'll always be a NULL terminator at the end of argv.
416 if ((pclp->m_argc + 2) > pclp->m_argv_len)
417 {
418 new_argv = (char **) malloc(new_argv_len * sizeof(char*)); // get a longer arg-vector
419 if (new_argv == NULL)
420 {
421 pclp->m_isValid = FALSE;
422 return;
423 }
424 for(i=0; i<new_argv_len; i++)
425 {
426 new_argv[i] = (char *) malloc(MAX_DN_BYTES * sizeof(char));
427 if (new_argv[i] == NULL)
428 {
429 for(j=0; j<i; j++)
430 {
431 if(new_argv[j])
432 {
433 free(new_argv[j]);
434 new_argv[j] = NULL;
435 }
436 }
437 if(new_argv)
438 {
439 free(new_argv);
440 new_argv = NULL;
441 }
442
443 pclp->m_isValid = FALSE;
444 return;
445 }
446 }
447
448 for (i=0; i<pclp->m_argc; i++)
449 strcpy(new_argv[i], pclp->m_argv[i]); // copy old arg strings
450
451 for(i=0; i<(pclp->m_argv_len); i++)
452 {
453 if(pclp->m_argv[i])
454 {
455 free(pclp->m_argv[i]);
456 pclp->m_argv[i] = NULL;
457 }
458 }
459 if (pclp->m_argv != NULL)
460 {
461 free(pclp->m_argv);
462 pclp->m_argv = NULL;
463 }
464
465
466 pclp->m_argv = new_argv;
467 pclp->m_argv_len = new_argv_len;
468
469 }
470
471 // Once m_argv is guaranteed long enough, appending the argument is a direct job.
472 strcpy(pclp->m_argv[pclp->m_argc], new_arg); // Appended the new argument.
473 pclp->m_argc++; // Increment the number of parameters appended.
474
475 // The char array is emptied for all elements upto the end so that there are no junk characters.
476 // If this is not done, then the issue is like this:
477 // - Simple perl command like "perl" on the system console works fine for the first time.
478 // - When it is given the second time, a new blank screen should come up which also
479 // allows for editing. This was not consistently working well.
480 // More so when the command was like, "perl ", that is the name "perl"
481 // followed by a few blank spaces. It used to give error in opening file and
482 // would give some junk as the filename unable to open.
483 // Once the below fix was done, it is working fine.
484 for(i=pclp->m_argc; i<pclp->m_argv_len; i++)
485 strncpy(pclp->m_argv[i], "", (MAX_DN_BYTES * sizeof(char))); // MAX_DN_BYTES is the size of pclp->m_argv[].
486
487
488 // Fix for empty command line double quote abend - perl <.pl> ""
489 if ((new_arg==NULL) || ((strlen(new_arg))<=0))
490 {
491 pclp->m_argc--; // Decrement the number of parameters appended.
492 pclp->m_isValid = FALSE;
493 return;
494 }
495
496
497 return;
498}
499
500
501
502/*============================================================================================
503
504 Function : fnSkipToken
505
506 Description : This function collects the next command-line argument, breaking on
507 unquoted white space. The quote symbols are copied into the output.
508 White space has already been skipped.
509
510 Parameters : s (IN) - Input string in which the token is skipped.
511 r (IN) - The resultant return string.
512
513 Returns : String.
514
515==============================================================================================*/
516
517char *fnSkipToken(char *s, char *r)
518{
519 register char *t=NULL;
520 register char quote = '\0'; // NULL, single quote, or double quote
521 char ch = '\0';
522
523 for (t=s; t[0]; t++)
524 {
525 ch = t[0];
526 if (!quote)
527 {
528 if (isspace(ch)) // if unquoted whitespace...
529 {
530 break; // ...end of token found
531 }
532 else if (ch=='"' || ch=='\'') // if opening quote...
533 {
534 quote = ch; // ...enter quote mode
535 }
536 }
537 else
538 {
539 if (ch=='\\' && t[1]==quote) // if escaped quote...
540 {
541 t++; // ...skip backslash
542 }
543 else if (ch==quote) // if close quote...
544 {
545 quote = 0; // ...leave quote mode
546 }
547 }
548 }
549
550 r = fnStashString(s, r, t-s); // get heap-allocated token string
551 t = fnSkipWhite(t); // skip any trailing white space
552 s = t; // return updated source pointer
553
554 s2 = t; // return updated global source pointer
555
556 return r; // return heap-allocated token string
557}
558
559
560
561/*============================================================================================
562
563 Function : fnScanToken
564
565 Description : This function collects the next command-line argument, breaking on
566 unquoted white space or I/O redirection symbols. Quote symbols are not
567 copied into the output.
568 When called, any leading white space has already been skipped.
569
570 Parameters : x (IN) - Input string in which the token is scanned.
571 r (IN) - The resultant return string.
572
573 Returns : String.
574
575==============================================================================================*/
576
577char *fnScanToken(char *x, char *r)
578{
579 register char *s = x; // input string position
580 register char *t = x; // output string position
581 register char quote = '\0'; // either NULL, or single quote, or double quote
582 register char ch = '\0';
583 register char c = '\0';
584
585 while (*s)
586 {
587 ch = *s; // invariant: ch != 0
588
589 // look to see if we've reached the end of the token
590 if (!quote) // but don't look for token break if we're inside quotes
591 {
592 if (isspace(ch))
593 break; // break on whitespace
594 if (ch=='>')
595 break; // break on ">" (redirect stdout)
596 if (ch=='<')
597 break; // break on "<" (redirect stdin)
598 if (ch=='&' && x[1]=='>')
599 break; // break on "&>" (redirect both stdout & stderr)
600 }
601
602 // process the next source character
603 if (ch=='\\' && (c=s[1]) && (c=='\\'||c=='>'||c=='<'||c==quote))
604 {
605 //-----------------if an escaped '\\', '>', '<', or quote...
606 s++; // ...skip over the backslash...
607 *t++ = *s++; // ...and copy the escaped character
608 }
609 else if (ch==quote) // (won't match unless inside quotes because invariant ch!=0)
610 {
611 //-----------------if close quote...
612 s++; // ...skip over the quote...
613 quote=0; // ...and leave quote mode
614 }
615 else if (!quote && (ch=='"' || ch=='\''))
616 {
617 //-----------------if opening quote...
618 quote = *s++; // ...enter quote mode (remembering quote char, and skipping the quote)
619 }
620 else
621 { //----------if normal character...
622 *t++ = *s++; // ...copy the character
623 }
624 }
625
626 // clean up return values
627 r = fnStashString(x, r, t-x); // get heap-allocated token string
628 s = fnSkipWhite(s); // skip any trailing white space
629 x = s; // return updated source pointer
630
631 s1 = s; // return updated global source pointer
632
633 return r;
634}
635
636
637
638/*============================================================================================
639
640 Function : fnStashString
641
642 Description : This function return the heap-allocated token string.
643
644 Parameters : s (IN) - Input string from which the token is extracted.
645 buffer (IN) - Return string.
646 length (IN) - Length of the token to be extracted.
647
648 Returns : String.
649
650==============================================================================================*/
651
652char *fnStashString(char *s, char *buffer, int length)
653{
654 if (length <= 0)
655 {
656 // Copy "" instead of NULL since "" indicates that there is memory allocated having no/null value.
657 // NULL indicates that there is no memory allocated to it!
658 strcpy(buffer, "");
659 }
660 else
661 {
662 strncpy(buffer, s, length);
663 buffer[length] = '\0';
664 }
665
666 return buffer;
667}
668
669
670
671/*============================================================================================
672
673 Function : fnDeleteArgument
674
675 Description : This function deletes an argument (that was originally appended) from the list.
676
677 Parameters : pclp (IN) - CommandLine structure.
678 index (IN) - Index of the argument to be deleted.
679
680 Returns : Nothing.
681
682==============================================================================================*/
683
684void fnDeleteArgument(PCOMMANDLINEPARSER pclp, int index)
685{
686 int i = index;
687
688
689 // If index is greater than the no. of arguments, just return.
690 if (index >= pclp->m_argc)
691 return;
692
693 // Move all the arguments after the index one up.
694 while(i < (pclp->m_argv_len-1))
695 {
696 strcpy(pclp->m_argv[i], pclp->m_argv[i+1]);
697 i++;
698 }
699
700
701 // Delete the last one and free memory.
702 if ( pclp->m_argv[i] )
703 {
704 free(pclp->m_argv[i]);
705 pclp->m_argv[i] = NULL;
706 }
707
708
709 pclp->m_argc--; // Decrement the number of arguments.
710 pclp->m_argv_len--;
711
712 return;
713}
714
715
716
717/*============================================================================================
718
719 Function : fnMy_MkTemp
720
721 Description : This is a standard ANSI C mktemp for NetWare
722
723 Parameters : templatestr (IN) - Input temp filename.
724
725 Returns : String.
726
727==============================================================================================*/
728
729char* fnMy_MkTemp(char* templatestr)
730{
731 char* pXs=NULL;
732 char numbuf[50]={'\0'};
733 int count=0;
734 char* pPid=NULL;
735
736 char termchar = '\0';
737 char letter = 'a';
78196db0 738 char letter1 = 'a';
2986a63f
JH
739
740
741 if (templatestr && (pXs = strstr(templatestr, (char *)"XXXXXX")))
742 {
743 // generate temp name
744 termchar = pXs[6];
745 ltoa(GetThreadID(), numbuf, 16);
746// numbuf[sizeof(numbuf)-1] = '\0';
747 numbuf[strlen(numbuf)-1] = '\0';
748 // beware! thread IDs are 8 hex digits on NW 4.11 and only the
749 // lower digits seem to change, whereas on NW 5 they are in the
750 // range of < 1000 hex or 3 hex digits in length. So the following
751 // logic ensures we use the least significant portion of the number.
752 if (strlen(numbuf) > 5)
753 pPid = &numbuf[strlen(numbuf)-5];
754 else
755 pPid = numbuf;
756
78196db0
JH
757/**
758 Backtick operation uses temp files that are stored under DEFPERLTEMP directory.
759 They are temporarily used and then cleaned up after usage.
760 In the process of operation to allow for more temp files, the below logic is used.
761 This allows 26 files (like, pla00015.tmp through plz00015.tmp) plus
762 26x26=676 (like, plaa0015.tmp through plzz0015.tmp)
763**/
764
2986a63f
JH
765 letter = 'a';
766 do
767 {
768 sprintf(pXs, (char *)"%c%05.5s", letter, pPid);
769 pXs[6] = termchar;
770 if (access(templatestr, 0) != 0) // File does not exist
771 {
772 return templatestr;
773 }
774 letter++;
775 } while (letter <= 'z');
776
78196db0
JH
777 letter1 = 'a';
778 do
779 {
780 letter = 'a';
781 do
782 {
783 sprintf(pXs, (char *)"%c%c%04.5s", letter1, letter, pPid);
784 pXs[6] = termchar;
785 if (access(templatestr, 0) != 0) // File does not exist
786 {
787 return templatestr;
788 }
789 letter++;
790 } while (letter <= 'z');
791 letter1++;
792 } while (letter1 <= 'z');
793
2986a63f
JH
794 errno = ENOENT;
795 return NULL;
796 }
797 else
798 {
799 errno = EINVAL;
800 return NULL;
801 }
802}
803
804
805
806/*============================================================================================
807
808 Function : fnSystemCommand
809
810 Description : This function constructs a system command from the given
811 null-terminated argv array and runs the command on the system console.
812
813 Parameters : argv (IN) - Array of input commands.
814 argc (IN) - Number of input parameters.
815
816 Returns : Nothing.
817
818==============================================================================================*/
819
820void fnSystemCommand (char** argv, int argc)
821{
822 // calculate the size of a temp buffer needed
823 int k = 0;
824 int totalSize = 0;
825 int bytes = 0;
826 char* tempCmd = NULL;
827 char* tptr = NULL;
828
829
830 for(k=0; k<argc; k++)
831 totalSize += strlen(argv[k]) + 1;
832
833 tempCmd = (char *) malloc((totalSize+1) * sizeof(char));
834 if (!tempCmd)
835 return;
836 tptr = tempCmd;
837
838 for(k=0; k<argc; k++)
839 tptr += sprintf(tptr, (char *)"%s ", argv[k]);
840 *tptr = 0;
841
842 if (stricmp(argv[0], PERL_COMMAND_NAME) == 0)
843 fnInternalPerlLaunchHandler(tempCmd); // Launch perl.
844 else
845 system(tempCmd);
846
847
848 free(tempCmd);
849 tempCmd = NULL;
850 return;
851}
852