1 /*
2 ** This file is placed into the public domain on February 22, 1996, by
3 ** its author: Carey Bloodworth
4 **
5 ** Modifications:
6 **
7 ** 07-Dec-1999 by
8 ** Bob Stout & Jon Guthrie
9 ** General cleanup, use NUL (in SNIPTYPE.H) instead of NULL where appropriate.
10 ** Allow spaces in tag names.
11 ** Allow strings in quotes.
12 ** Allow trailing comments.
13 ** Allow embedded comment separator(s) in quoted strings.
14 ** Add comments.
15 ** ReadCfg() now returns the number of variables found if no error occurred.
16 ** Changed integer type to short.
17 ** Use cant() calls in lieu of fopen() calls,
18 ** include ERRORS.H for prototype.
19 ** Fix parsing error with null string data.
20 */
21
22 #include <stdio.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <stdlib.h>
26
27 #include "ini.h"
28
29 #define BUFFERSIZE (INI_LINESIZE + 2)
30
31 enum LineTypes {
32 EmptyLine = 0, CommentLine = 1, InSection = 2, NotInSection = 3,
33 NewSection = 4, FoundSection = 5, LeavingSection = 6
34 };
35
36
37 /*
38 ** ferrorf()
39 **
40 ** Prints error message with printf() formatting syntax, then a colon,
41 ** then a message corresponding to the value of errno, then a newline.
42 ** Output is to filehandle.
43 **
44 ** Public Domain by Mark R. Devlin, free usage is permitted.
45 */
46
47 #include <stdlib.h>
48 #include <stdarg.h>
49 #include <string.h>
50 #include <errno.h>
51
52 int ferrorf(FILE *filehandle, const char *format, ...)
53 {
54 int vfp, fp;
55 va_list vargs;
56
57 vfp = fp = 0;
58 va_start(vargs, format);
59 vfp = vfprintf(filehandle, format, vargs);
60 va_end(vargs);
61 fp = fprintf(filehandle, ": %s\n", sys_errlist[errno]);
62 return ((vfp==EOF || fp==EOF) ? EOF : (vfp+fp));
63 }
64
65 /*
66 ** chgext.c - Change a file's extension
67 **
68 ** public domain by Bob Stout
69 **
70 ** Arguments: 1 - Pathname
71 ** 2 - Old extension (NULL if don't care)
72 ** 3 - New extension
73 **
74 ** Returns: Pathname or NULL if failed
75 **
76 ** Note: Pathname buffer must be long enough to append new extension
77 **
78 ** Side effect: Converts Unix style pathnames to DOS style
79 */
80
81 char *chgext(char *path, char *oldext, char *newext)
82 {
83 char *p;
84
85 /* Convert to DOS-style path name */
86
87 for (p = path; *p; ++p)
88 if ('/' == *p)
89 *p = '\\';
90
91 /* Find extension or point to end for appending */
92
93 if (NULL == (p = strrchr(path, '.')) || NULL != strchr(p, '\\'))
94 p = strcpy(&path[strlen(path)], ".");
95 ++p;
96
97 /* Check for old extension */
98
99 if (oldext && strcmp(p, oldext))
100 return NULL;
101
102 /* Add new extension */
103
104 while ('.' == *newext)
105 ++newext;
106 strncpy(p, newext, 3);
107
108 /*
109 ** Added to insure string is properly terminated. Without this, if
110 ** the new extension is longer than the old, we lose the terminator.
111 */
112
113 p[strlen(newext)] = '\0';
114
115 return path;
116 }
117
118
119
120 /*
121 ** cant() - An fopen() replacement with error trapping
122 **
123 ** Call just as you would fopen(), but make sure your exit functions are
124 ** registered with atexit().
125 **
126 ** public domain by Bob Stout
127 */
128
129 FILE *cant(char *fname, char *fmode)
130 {
131 FILE *fp;
132
133 if (NULL == (fp = fopen(fname, fmode)))
134 {
135 ferrorf(stderr, "Can't open %s", fname);
136 exit(EXIT_FAILURE);
137 }
138 return fp;
139 }
140
141
142
143 /*
144 ** StripLeadingSpaces() - Strips leading spaces from a string.
145 **
146 ** Paramters: 1 - String.
147 **
148 ** Returns: Pointer to first non-whitespace character in the string.
149 **
150 ** Note: This does not modify the original string.
151 */
152
153 static char *StripLeadingSpaces(char *string)
154 {
155 if (!string || (0 == strlen(string)))
156 return NULL;
157
158 while ((*string) && (isspace(*string)))
159 string++;
160 return string;
161 }
162
163 /*
164 ** StripTrailingSpaces() - Strips traling spaces from a string.
165 **
166 ** Paramters: 1 - String.
167 **
168 ** Returns: Pointer to the string.
169 **
170 ** Note: This does modify the original string.
171 */
172
173 static char *StripTrailingSpaces(char *string)
174 {
175 if (!string || (0 == strlen(string)))
176 return NULL;
177
178 while (isspace(LAST_CHAR(string)))
179 LAST_CHAR(string) = NUL;
180
181 return string;
182 }
183
184 /*
185 ** ReadLine() - Read a line from the active file
186 **
187 ** Paramters: 1 - File pointer of the active file.
188 ** 2 - Pointer to the line's storage.
189 **
190 ** Returns: Length of line or EOF.
191 **
192 ** Side effects: Strips newline characters (DOS, Unix, or Mac).
193 */
194
195 static int ReadLine(FILE *fp, char *line)
196 {
197 char *cp;
198
199 cp = fgets(line, INI_LINESIZE, fp);
200
201 if (NULL == cp)
202 {
203 *line = 0;
204 return EOF;
205 }
206 if (feof(fp))
207 {
208 *line = 0;
209 return EOF;
210 }
211 if (0 != ferror(fp))
212 {
213 *line = 0;
214 return EOF;
215 }
216
217 /*
218 ** Allow both DOS and Unix style newlines.
219 */
220
221 while (strlen(line) && strchr("\n\r", LAST_CHAR(line)))
222 LAST_CHAR(line) = NUL;
223
224 return strlen(line);
225 }
226
227 /*
228 ** StrEq() - Case-insensitive string compare.
229 **
230 ** Paramters: 1 - First string.
231 ** 2 - second string.
232 **
233 ** Returns: True_ if strings match, else False_.
234 */
235
236 static Boolean_T StrEq(char *s1, char *s2)
237 {
238 while (tolower(*s1) == tolower(*s2))
239 {
240 if (NUL == *s1)
241 return True_;
242 s1++;
243 s2++;
244 }
245 return False_;
246 }
247
248 /*
249 ** ParseLine() - This routine divides the line into two parts. The
250 ** variable, and the 'string'.
251 **
252 ** Paramters: 1 - The line to parse:
253 ** 2 - Pointer to variable name storage.
254 ** 3 = Pointer to textual data storage.
255 **
256 ** Returns: Nothing.
257 */
258
259 static void ParseLine(char *line, char *var, char *data)
260 {
261 int len = 0;
262
263 line = StripLeadingSpaces(line);
264 strcpy(var, line);
265 strcpy(data, "");
266
267 while (*line)
268 {
269 char *ptr;
270
271 if (/*isspace(*line) || */ ('=' == *line))
272 {
273 var[len] = 0;
274 var = StripTrailingSpaces(var);
275 line = StripLeadingSpaces(line+1);
276
277 /* Remove a possible '=' */
278 /* This could allow var==string in some cases. No big deal*/
279
280 while (line && '=' == *line)
281 line = StripLeadingSpaces(line+1);
282 if (line)
283 {
284 strcpy(data, line);
285 if (NULL != (ptr = strrchr(data, ';')) &&
286 NULL == strchr(ptr, '\"'))
287 {
288 *ptr = NUL;
289 }
290 StripTrailingSpaces(data);
291 }
292 return;
293 }
294 else
295 {
296 line++;
297 len++;
298 }
299 }
300 }
301
302 /*
303 ** SectionLine() - This routine checks each line of the file and identifies
304 ** only those lines that are within the right section.
305 **
306 ** Paramters: 1 - The line to scan.
307 ** 2 - The specific section wanted.
308 ** 3 - The current section name.
309 **
310 ** Returns: Line type.
311 */
312
313 static enum LineTypes SectionLine(char *line,
314 char *SectionWanted,
315 char *CurrentSection)
316 {
317 enum LineTypes linetype;
318
319 line = StripLeadingSpaces(line);
320 if (!line || NUL == *line)
321 return EmptyLine;
322
323 /*
324 ** Comments are started with a "%", ";" or "#"
325 */
326
327 if (';' == *line)
328 return CommentLine;
329 if ('%' == *line)
330 return CommentLine;
331 if ('#' == *line)
332 return CommentLine;
333
334 if ('[' == line[0]) /* Section Header */
335 {
336 linetype = NewSection;
337 if (StrEq(CurrentSection, SectionWanted))
338 linetype = LeavingSection;
339
340 strcpy(CurrentSection, line);
341 if (StrEq(line, SectionWanted))
342 linetype = FoundSection;
343 }
344 else
345 { /* Just a regular line */
346 linetype = NotInSection;
347 if (StrEq(CurrentSection, SectionWanted))
348 linetype = InSection;
349 }
350
351 return linetype;
352 }
353
354 /*
355 ** ReadCfg() - Reads a .ini / .cfg file. May read multiple lines within the
356 ** specified section.
357 **
358 ** Paramters: 1 - File name
359 ** 2 - Section name
360 ** 3 - Array of CfgStruct pointers
361 **
362 ** Returns: Number of variables located
363 ** -1 if any type spec failed
364 ** -2 for any type of file error
365 */
366
367 int ReadCfg(const char *FileName, char *SectionName, struct CfgStruct *MyVars)
368 {
369 FILE *CfgFile;
370 char line[BUFFERSIZE];
371 char SectionWanted[BUFFERSIZE];
372 char CurrentSection[BUFFERSIZE];
373 enum LineTypes linetype;
374 char var[BUFFERSIZE];
375 char data[BUFFERSIZE], *dp;
376 int retval = 0;
377 struct CfgStruct *mv;
378
379 CfgFile = cant((char *)FileName, "r");
380
381 strcpy(CurrentSection, "[]");
382 sprintf(SectionWanted, "[%s]", SectionName);
383
384 while (EOF != ReadLine(CfgFile, line))
385 {
386 linetype = SectionLine(line, SectionWanted, CurrentSection);
387 switch (linetype)
388 {
389 case EmptyLine:
390 break; /* Nothing to parse */
391
392 case CommentLine:
393 break; /* Nothing to parse */
394
395 case InSection: /* In our section, parse it. */
396 {
397 ParseLine(line, var, data);
398
399 for (mv = MyVars; mv->Name; ++mv)
400 {
401 if (StrEq(mv->Name, var))
402 {
403 switch (mv->VarType)
404 {
405 case Cfg_String:
406 if ('\"' == *data)
407 {
408 dp = data + 1;
409 data[strlen(data)-1] = NUL;
410 }
411 else dp = data;
412 /*
413 ** Use sprintf to assure embedded
414 ** escape sequences are handled.
415 */
416 sprintf(mv->DataPtr, dp);
417 ++retval;
418 break;
419
420 case Cfg_Byte:
421 *((unsigned char*)mv->DataPtr) =
422 (unsigned char)atoi(data);
423 ++retval;
424 break;
425
426 case Cfg_Ushort:
427 *((unsigned int*)mv->DataPtr) =
428 (unsigned int)atoi(data);
429 ++retval;
430 break;
431
432 case Cfg_Short:
433 *((int*)mv->DataPtr) = atoi(data);
434 ++retval;
435 break;
436
437 case Cfg_Ulong:
438 *((unsigned long*)mv->DataPtr) =
439 (unsigned long)atol(data);
440 ++retval;
441 break;
442
443 case Cfg_Long:
444 *((long*)mv->DataPtr) = atol(data);
445 ++retval;
446 break;
447
448 case Cfg_Double:
449 *((double*)mv->DataPtr) = atof(data);
450 ++retval;
451 break;
452
453 case Cfg_Boolean:
454 *((int*)mv->DataPtr) = 0;
455 data[0] = tolower(data[0]);
456 if (('y' == data[0]) || ('t' == data[0]))
457 *((int*)mv->DataPtr) = 1;
458 ++retval;
459 break;
460
461 case Cfg_I_Array:
462 {
463 int *ip;
464 char *str;
465
466 ip = ((int*)mv->DataPtr);
467 str = strtok(data, " ,\t");
468 while (NULL != str)
469 {
470 *ip = atoi(str);
471 ip++;
472 str = strtok(NULL, " ,\t");
473 }
474 ++retval;
475 break;
476 }
477
478 default:
479 #ifdef TEST
480 printf("Unknown conversion type\n");
481 #endif
482 retval = -1;
483 break;
484 }
485 }
486 if (-1 == retval)
487 break;
488 };
489
490 /*
491 ** Variable wasn't found. If we don't want it,
492 ** then ignore it
493 */
494 }
495 case NotInSection:
496 break; /* Not interested in this line */
497
498 case NewSection:
499 break; /* Who cares? It's not our section */
500
501 case FoundSection:
502 break; /* We found our section! */
503
504 case LeavingSection:
505 break; /* We finished our section! */
506 }
507
508 if (-1 == retval)
509 break;
510 }
511
512 if (ferror(CfgFile))
513 {
514 fclose(CfgFile);
515 return -2;
516 }
517 else
518 {
519 fclose(CfgFile);
520 return retval;
521 }
522 }
523
524 /*
525 ** SearchCfg() - Search an .ini / .cfg file for a specific single datum.
526 **
527 ** Parameters: 1 - File name.
528 ** 2 - Section name.
529 ** 3 - Variable to find.
530 ** 4 - Pointer to variable's storage.
531 ** 5 - Type of vatiable.
532 **
533 ** Returns: 1 if succesful
534 ** 0 if section/variable not found
535 ** -1 if invalid type spec
536 ** -2 for file error
537 */
538
539 int SearchCfg(const char *FileName,
540 char *SectionName,
541 char *VarName,
542 void *DataPtr,
543 enum CfgTypes VarType
544 )
545 {
546 struct CfgStruct MyVars[2];
547
548 MyVars[0].Name = VarName;
549 MyVars[0].DataPtr = DataPtr;
550 MyVars[0].VarType = VarType;
551
552 MyVars[1].Name = MyVars[1].DataPtr = NULL;
553
554 return ReadCfg(FileName, SectionName, MyVars);
555 }
556
557 /*
558 ** UpdateCfg() - This will update a variable in a specific section in your
559 ** .ini file. It will do so safely by copying it to a new file,
560 ** and when finished, will delete the old one and rename the
561 ** new one to the correct name. If any fatal error occurs, it
562 ** will return a -1 to indiate failure. I generally don't care
563 ** why it failed, just knowing that it failed is usually enough.
564 **
565 ** Paramters: 1 - File name.
566 ** 2 - Section name.
567 ** 3 - Variable tag.
568 ** 4 - Pointer to textual representation of the variable's value.
569 **
570 ** Returns: -1 if a file error occurred, else 0.
571 **
572 ** Notes: 1. If the section doesn't yet exist in the file, it will be added.
573 ** 2. If the variable doesn't yet exist in the file, it will be added.
574 ** 3. New variables are created at the end of existing sections, or
575 ** on the last line before the first section name in the case where
576 ** the section name is "".
577 ** 4. New sections are created at the end of the file.
578 */
579
580 int UpdateCfg(const char *FileName,
581 char *SectionName,
582 char *VarWanted,
583 char *NewData)
584 {
585 FILE *CfgFile;
586 char line[BUFFERSIZE];
587 char SectionWanted[BUFFERSIZE];
588 char CurrentSection[BUFFERSIZE];
589 enum LineTypes linetype;
590 char var[BUFFERSIZE];
591 char data[BUFFERSIZE];
592 char TempFileName[FILENAME_MAX];
593 FILE *NewCfgFile;
594 int Error = 0;
595 int updated = 0;
596
597 CfgFile = cant((char *)FileName, "r");
598
599 strcpy(TempFileName, FileName);
600 chgext(TempFileName, NULL, "tmp");
601 NewCfgFile = cant(TempFileName, "w");
602
603 strcpy(CurrentSection, "[]");
604 sprintf(SectionWanted, "[%s]", SectionName);
605
606 while (EOF != ReadLine(CfgFile, line))
607 {
608 linetype = SectionLine(line, SectionWanted, CurrentSection);
609 switch (linetype)
610 {
611 case InSection: /* In our section, parse it. */
612 ParseLine(line, var, data);
613 if ((StrEq(var, VarWanted)) && (!updated))
614 {
615 strncpy(data, NewData, BUFFERSIZE);
616 data[BUFFERSIZE-1] = NUL;
617 updated = 1;
618 }
619 fprintf(NewCfgFile, "%s = %s\n", var, data);
620 break;
621
622 case EmptyLine: /* Fall Through. Just copy it. */
623 case CommentLine: /* Fall Through. Just copy it. */
624 case NotInSection: /* Fall Through. Just copy it. */
625 case NewSection: /* Fall Through. Just copy it. */
626 case FoundSection: /* Fall Through. Just copy it. */
627 fprintf(NewCfgFile, "%s\n", line);
628 break;
629
630 case LeavingSection: /* Leaving section, may have to add it */
631 if (!updated) /* Variable wasn't found, we have */
632 { /* to add it. */
633 fprintf(NewCfgFile, "%s = %s\n", VarWanted, NewData);
634 updated = 1;
635 }
636 /*
637 ** Now print current line
638 */
639
640 fprintf(NewCfgFile, "%s\n", line);
641 break;
642 }
643 }
644
645 /*
646 ** Our section may not have even been there, in which case we have
647 ** to add both the variable and the section itself.
648 */
649
650 if (!updated)
651 { /* We may have hit EOF while still in our section. */
652 /* If so, we don't need to add the section header. */
653 if (!StrEq(CurrentSection, SectionWanted))
654 fprintf(NewCfgFile, "%s\n", SectionWanted);
655 fprintf(NewCfgFile, "%s = %s\n", VarWanted, NewData);
656 }
657
658 if (ferror(CfgFile))
659 Error = -1;
660 if (ferror(NewCfgFile))
661 Error = -1;
662 fclose(CfgFile);
663 fclose(NewCfgFile);
664
665 if (!Error)
666 {
667 if (remove(FileName))
668 return -1;
669 if (rename(TempFileName, FileName))
670 return -1;
671 }
672 return Error;
673 }
674
675 #ifdef TEST
676
677 #include <stdlib.h>
678
679 #if defined(MSDOS) || defined (_MSDOS_)
680 #define PINI_fname "prices.ini"
681 #else
682 #define PINI_fname "/home/mars_nwe/engr/disp/prices.ini"
683 #endif
684
685 FILE *log_ = stderr;
686
687 main()
688 {
689 char Line[INI_LINESIZE];
690 int Int;
691 long Long;
692 double Double;
693 Boolean_T Bool;
694 struct CfgStruct this_var;
695 FILE* tst;
696 long prices[2];
697
698
699 /*
700 ** First work with a test file
701 */
702 tst = cant("test.ini", "w");
703 fputs("[Section 1]\n", tst);
704 fputs("[Section 2]\n", tst);
705 fputs("[Section 3]\n", tst);
706 fputs("[Section 4]\n", tst);
707 fputs("[Section 5]\n", tst);
708 fputs("[Section 6]\n", tst);
709 fclose(tst);
710 puts("Updating the test configuration file");
711
712 puts("Updating section 1");
713 UpdateCfg("test.ini", "Section 1", "string #1", "section 1 test");
714 puts("Updating section 2");
715 UpdateCfg("test.ini", "Section 2", "short #2", "2");
716 puts("Updating section 3");
717 UpdateCfg("test.ini", "Section 3", "long #3", "3");
718 UpdateCfg("test.ini", "Section 4", "double #4", "4.4");
719 UpdateCfg("test.ini", "Section 5", "boolean #5", "Y");
720 UpdateCfg("test.ini", "Section 6", "boolean #6", "N");
721 UpdateCfg("test.ini", "", "global string", "\"Hello, world!\" ;Comment");
722
723 puts("I've finished the updates, now to try to get the data back");
724
725 this_var.Name = "global string";
726 this_var.DataPtr = Line;
727 this_var.VarType = Cfg_String;
728 printf("ReadCfg(0) returned %d; Line=\n",
729 ReadCfg("test.ini", "", &this_var));
730 puts(Line);
731
732 this_var.Name = "string #1";
733 this_var.DataPtr = Line;
734 this_var.VarType = Cfg_String;
735 printf("ReadCfg(1) returned %d; Line=\n",
736 ReadCfg("test.ini", "Section 1", &this_var));
737 puts(Line);
738
739 this_var.Name = "short #2";
740 this_var.DataPtr = ∬
741 this_var.VarType = Cfg_Short;
742 printf("ReadCfg(2) returned %d; Value= ",
743 ReadCfg("test.ini", "Section 2", &this_var));
744 printf("%d\n", Int);
745
746 this_var.Name = "long #3";
747 this_var.DataPtr = &Long;
748 this_var.VarType = Cfg_Long;
749 printf("ReadCfg(3) returned %d; Value = ",
750 ReadCfg("test.ini", "Section 3", &this_var));
751 printf("%ld\n", Long);
752
753 this_var.Name = "double #4";
754 this_var.DataPtr = &Double;
755 this_var.VarType = Cfg_Double;
756 printf("ReadCfg(4) returned %d; Value = ",
757 ReadCfg("test.ini", "Section 4", &this_var));
758 printf("%f\n", Double);
759
760 this_var.Name = "boolean #5";
761 this_var.DataPtr = &Bool;
762 this_var.VarType = Cfg_Boolean;
763 printf("ReadCfg(5) returned %d; Value = ",
764 ReadCfg("test.ini", "Section 5", &this_var));
765 printf("%c\n", Bool ? 'T' : 'F');
766
767 this_var.Name = "boolean #6";
768 this_var.DataPtr = &Bool;
769 this_var.VarType = Cfg_Boolean;
770 printf("ReadCfg(6) returned %d; Value = ",
771 ReadCfg("test.ini", "Section 6", &this_var));
772 printf("%c\n", Bool ? 'T' : 'F');
773
774 /*
775 ** Look for non-existant sections and/or variables
776 */
777
778 Line[0] = NUL;
779 this_var.Name = "string #99";
780 this_var.DataPtr = Line;
781 this_var.VarType = Cfg_String;
782 printf("ReadCfg(99) returned %d; Line=\n",
783 ReadCfg("test.ini", "Section 99", &this_var));
784 puts(Line);
785
786 Line[0] = NUL;
787 this_var.Name = "string #99";
788 this_var.DataPtr = Line;
789 this_var.VarType = Cfg_String;
790 printf("ReadCfg(0/99) returned %d; Line=\n",
791 ReadCfg("test.ini", "", &this_var));
792 puts(Line);
793
794 Line[0] = NUL;
795 this_var.Name = "string #99";
796 this_var.DataPtr = Line;
797 this_var.VarType = Cfg_String;
798 printf("ReadCfg(1/99) returned %d; Line=\n",
799 ReadCfg("test.ini", "Section 1", &this_var));
800 puts(Line);
801
802 Line[0] = NUL;
803 this_var.Name = "string #1";
804 this_var.DataPtr = Line;
805 this_var.VarType = Cfg_String;
806 printf("ReadCfg(1/100) returned %d; Line=\n",
807 ReadCfg("test.ini", "Section 100", &this_var));
808 puts(Line);
809
810 /*
811 ** Next, add a section and new variables
812 */
813
814 UpdateCfg("test.ini", "", "new global variable", "abc");
815 UpdateCfg("test.ini", "Section -1", "new variable", "xyz");
816
817 /*
818 ** Next work with a sample real PRICES.INI file
819 */
820
821 this_var.Name = "Price of #1";
822 this_var.DataPtr = &prices[0];
823 this_var.VarType = Cfg_Long;
824 printf("ReadCfg(1) returned %d; Value = ",
825 ReadCfg(PINI_fname, "", &this_var));
826 printf("%ld\n", prices[0]);
827
828 this_var.Name = "Price of #2";
829 this_var.DataPtr = &prices[1];
830 this_var.VarType = Cfg_Long;
831 printf("ReadCfg(2) returned %d; Value = ",
832 ReadCfg(PINI_fname, "", &this_var));
833 printf("%ld\n", prices[1]);
834
835 UpdateCfg("prices.ini", "", "Price of #2", "999");
836
837 this_var.Name = "Price of #2";
838 this_var.DataPtr = &prices[1];
839 this_var.VarType = Cfg_Long;
840 printf("ReadCfg(2) returned %d; Value = ",
841 ReadCfg(PINI_fname, "", &this_var));
842 printf("%ld\n", prices[1]);
843
844 UpdateCfg(PINI_fname, "", "Price of #2", "389");
845
846 this_var.Name = "Price of #2";
847 this_var.DataPtr = &prices[1];
848 this_var.VarType = Cfg_Long;
849 printf("ReadCfg(2) returned %d; Value = ",
850 ReadCfg(PINI_fname, "", &this_var));
851 printf("%ld\n", prices[1]);
852
853 /*
854 ** Finally, try an invalid file name
855 */
856
857 this_var.Name = "global string";
858 this_var.DataPtr = Line;
859 this_var.VarType = Cfg_String;
860 printf("ReadCfg(0) returned %d; Line=\n",
861 ReadCfg("none.ini", "", &this_var));
862 puts(Line);
863
864 return EXIT_SUCCESS;
865 }
866
867 #endif
868