1 
2 /*-------------------------------------------------------------------------*/
3 /**
4    @file    iniparser.c
5    @author  N. Devillard
6    @brief   Parser for ini files.
7 */
8 /*--------------------------------------------------------------------------*/
9 /*---------------------------- Includes ------------------------------------*/
10 #include <ctype.h>
11 #include "iniparser.h"
12 
13 /*---------------------------- Defines -------------------------------------*/
14 #define ASCIILINESZ         (1024)
15 #define INI_INVALID_KEY     ((char*)-1)
16 
17 /*---------------------------------------------------------------------------
18                         Private to this module
19  ---------------------------------------------------------------------------*/
20 /**
21  * This enum stores the status for each parsed line (internal use only).
22  */
23 typedef enum _line_status_ {
24     LINE_UNPROCESSED,
25     LINE_ERROR,
26     LINE_EMPTY,
27     LINE_COMMENT,
28     LINE_SECTION,
29     LINE_VALUE
30 } line_status ;
31 
32 /*-------------------------------------------------------------------------*/
33 /**
34   @brief    Convert a string to lowercase.
35   @param    s   String to convert.
36   @return   ptr to statically allocated string.
37 
38   This function returns a pointer to a statically allocated string
39   containing a lowercased version of the input string. Do not free
40   or modify the returned string! Since the returned string is statically
41   allocated, it will be modified at each function call (not re-entrant).
42  */
43 /*--------------------------------------------------------------------------*/
strlwc(const char * s)44 static char * strlwc(const char * s)
45 {
46     static char l[ASCIILINESZ+1];
47     int i ;
48 
49     if (s==NULL) return NULL ;
50     memset(l, 0, ASCIILINESZ+1);
51     i=0 ;
52     while (s[i] && i<ASCIILINESZ) {
53         l[i] = (char)tolower((int)s[i]);
54         i++ ;
55     }
56     l[ASCIILINESZ]=(char)0;
57     return l ;
58 }
59 
60 /*-------------------------------------------------------------------------*/
61 /**
62   @brief    Remove blanks at the beginning and the end of a string.
63   @param    s   String to parse.
64   @return   ptr to statically allocated string.
65 
66   This function returns a pointer to a statically allocated string,
67   which is identical to the input string, except that all blank
68   characters at the end and the beg. of the string have been removed.
69   Do not free or modify the returned string! Since the returned string
70   is statically allocated, it will be modified at each function call
71   (not re-entrant).
72  */
73 /*--------------------------------------------------------------------------*/
strstrip(const char * s)74 static char * strstrip(const char * s)
75 {
76     static char l[ASCIILINESZ+1];
77     char * last ;
78 
79     if (s==NULL) return NULL ;
80 
81     while (isspace((int)*s) && *s) s++;
82     memset(l, 0, ASCIILINESZ+1);
83     strcpy(l, s);
84     last = l + strlen(l);
85     while (last > l) {
86         if (!isspace((int)*(last-1)))
87             break ;
88         last -- ;
89     }
90     *last = (char)0;
91     return (char*)l ;
92 }
93 
94 /*-------------------------------------------------------------------------*/
95 /**
96   @brief    Get number of sections in a dictionary
97   @param    d   Dictionary to examine
98   @return   int Number of sections found in dictionary
99 
100   This function returns the number of sections found in a dictionary.
101   The test to recognize sections is done on the string stored in the
102   dictionary: a section name is given as "section" whereas a key is
103   stored as "section:key", thus the test looks for entries that do not
104   contain a colon.
105 
106   This clearly fails in the case a section name contains a colon, but
107   this should simply be avoided.
108 
109   This function returns -1 in case of error.
110  */
111 /*--------------------------------------------------------------------------*/
iniparser_getnsec(dictionary * d)112 int iniparser_getnsec(dictionary * d)
113 {
114     int i ;
115     int nsec ;
116 
117     if (d==NULL) return -1 ;
118     nsec=0 ;
119     for (i=0 ; i<d->size ; i++) {
120         if (d->key[i]==NULL)
121             continue ;
122         if (strchr(d->key[i], ':')==NULL) {
123             nsec ++ ;
124         }
125     }
126     return nsec ;
127 }
128 
129 /*-------------------------------------------------------------------------*/
130 /**
131   @brief    Get name for section n in a dictionary.
132   @param    d   Dictionary to examine
133   @param    n   Section number (from 0 to nsec-1).
134   @return   Pointer to char string
135 
136   This function locates the n-th section in a dictionary and returns
137   its name as a pointer to a string statically allocated inside the
138   dictionary. Do not free or modify the returned string!
139 
140   This function returns NULL in case of error.
141  */
142 /*--------------------------------------------------------------------------*/
iniparser_getsecname(dictionary * d,int n)143 char * iniparser_getsecname(dictionary * d, int n)
144 {
145     int i ;
146     int foundsec ;
147 
148     if (d==NULL || n<0) return NULL ;
149     foundsec=0 ;
150     for (i=0 ; i<d->size ; i++) {
151         if (d->key[i]==NULL)
152             continue ;
153         if (strchr(d->key[i], ':')==NULL) {
154             foundsec++ ;
155             if (foundsec>n)
156                 break ;
157         }
158     }
159     if (foundsec<=n) {
160         return NULL ;
161     }
162     return d->key[i] ;
163 }
164 
165 /*-------------------------------------------------------------------------*/
166 /**
167   @brief    Dump a dictionary to an opened file pointer.
168   @param    d   Dictionary to dump.
169   @param    f   Opened file pointer to dump to.
170   @return   void
171 
172   This function prints out the contents of a dictionary, one element by
173   line, onto the provided file pointer. It is OK to specify @c stderr
174   or @c stdout as output files. This function is meant for debugging
175   purposes mostly.
176  */
177 /*--------------------------------------------------------------------------*/
iniparser_dump(dictionary * d,FILE * f)178 void iniparser_dump(dictionary * d, FILE * f)
179 {
180     int     i ;
181 
182     if (d==NULL || f==NULL) return ;
183     for (i=0 ; i<d->size ; i++) {
184         if (d->key[i]==NULL)
185             continue ;
186         if (d->val[i]!=NULL) {
187             fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
188         } else {
189             fprintf(f, "[%s]=UNDEF\n", d->key[i]);
190         }
191     }
192     return ;
193 }
194 
195 /*-------------------------------------------------------------------------*/
196 /**
197   @brief    Save a dictionary to a loadable ini file
198   @param    d   Dictionary to dump
199   @param    f   Opened file pointer to dump to
200   @return   void
201 
202   This function dumps a given dictionary into a loadable ini file.
203   It is Ok to specify @c stderr or @c stdout as output files.
204  */
205 /*--------------------------------------------------------------------------*/
iniparser_dump_ini(dictionary * d,FILE * f)206 void iniparser_dump_ini(dictionary * d, FILE * f)
207 {
208     int     i ;
209     int     nsec ;
210     char *  secname ;
211 
212     if (d==NULL || f==NULL) return ;
213 
214     nsec = iniparser_getnsec(d);
215     if (nsec<1) {
216         /* No section in file: dump all keys as they are */
217         for (i=0 ; i<d->size ; i++) {
218             if (d->key[i]==NULL)
219                 continue ;
220             fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
221         }
222         return ;
223     }
224     for (i=0 ; i<nsec ; i++) {
225         secname = iniparser_getsecname(d, i) ;
226         iniparser_dumpsection_ini(d, secname, f) ;
227     }
228     fprintf(f, "\n");
229     return ;
230 }
231 
232 /*-------------------------------------------------------------------------*/
233 /**
234   @brief    Save a dictionary section to a loadable ini file
235   @param    d   Dictionary to dump
236   @param    s   Section name of dictionary to dump
237   @param    f   Opened file pointer to dump to
238   @return   void
239 
240   This function dumps a given section of a given dictionary into a loadable ini
241   file.  It is Ok to specify @c stderr or @c stdout as output files.
242  */
243 /*--------------------------------------------------------------------------*/
iniparser_dumpsection_ini(dictionary * d,char * s,FILE * f)244 void iniparser_dumpsection_ini(dictionary * d, char * s, FILE * f)
245 {
246     int     j ;
247     char    keym[ASCIILINESZ+1];
248     int     seclen ;
249 
250     if (d==NULL || f==NULL) return ;
251     if (! iniparser_find_entry(d, s)) return ;
252 
253     seclen  = (int)strlen(s);
254     fprintf(f, "\n[%s]\n", s);
255     sprintf(keym, "%s:", s);
256     for (j=0 ; j<d->size ; j++) {
257         if (d->key[j]==NULL)
258             continue ;
259         if (!strncmp(d->key[j], keym, seclen+1)) {
260             fprintf(f,
261                     "%-30s = %s\n",
262                     d->key[j]+seclen+1,
263                     d->val[j] ? d->val[j] : "");
264         }
265     }
266     fprintf(f, "\n");
267     return ;
268 }
269 
270 /*-------------------------------------------------------------------------*/
271 /**
272   @brief    Get the number of keys in a section of a dictionary.
273   @param    d   Dictionary to examine
274   @param    s   Section name of dictionary to examine
275   @return   Number of keys in section
276  */
277 /*--------------------------------------------------------------------------*/
iniparser_getsecnkeys(dictionary * d,char * s)278 int iniparser_getsecnkeys(dictionary * d, char * s)
279 {
280     int     seclen, nkeys ;
281     char    keym[ASCIILINESZ+1];
282     int j ;
283 
284     nkeys = 0;
285 
286     if (d==NULL) return nkeys;
287     if (! iniparser_find_entry(d, s)) return nkeys;
288 
289     seclen  = (int)strlen(s);
290     sprintf(keym, "%s:", s);
291 
292     for (j=0 ; j<d->size ; j++) {
293         if (d->key[j]==NULL)
294             continue ;
295         if (!strncmp(d->key[j], keym, seclen+1))
296             nkeys++;
297     }
298 
299     return nkeys;
300 
301 }
302 
303 /*-------------------------------------------------------------------------*/
304 /**
305   @brief    Get the number of keys in a section of a dictionary.
306   @param    d   Dictionary to examine
307   @param    s   Section name of dictionary to examine
308   @return   pointer to statically allocated character strings
309 
310   This function queries a dictionary and finds all keys in a given section.
311   Each pointer in the returned char pointer-to-pointer is pointing to
312   a string allocated in the dictionary; do not free or modify them.
313 
314   This function returns NULL in case of error.
315  */
316 /*--------------------------------------------------------------------------*/
iniparser_getseckeys(dictionary * d,char * s)317 char ** iniparser_getseckeys(dictionary * d, char * s)
318 {
319 
320     char **keys;
321 
322     int i, j ;
323     char    keym[ASCIILINESZ+1];
324     int     seclen, nkeys ;
325 
326     keys = NULL;
327 
328     if (d==NULL) return keys;
329     if (! iniparser_find_entry(d, s)) return keys;
330 
331     nkeys = iniparser_getsecnkeys(d, s);
332 
333     keys = (char**) malloc(nkeys*sizeof(char*));
334 
335     seclen  = (int)strlen(s);
336     sprintf(keym, "%s:", s);
337 
338     i = 0;
339 
340     for (j=0 ; j<d->size ; j++) {
341         if (d->key[j]==NULL)
342             continue ;
343         if (!strncmp(d->key[j], keym, seclen+1)) {
344             keys[i] = d->key[j];
345             i++;
346         }
347     }
348 
349     return keys;
350 
351 }
352 
353 /*-------------------------------------------------------------------------*/
354 /**
355   @brief    Get the string associated to a key
356   @param    d       Dictionary to search
357   @param    key     Key string to look for
358   @param    def     Default value to return if key not found.
359   @return   pointer to statically allocated character string
360 
361   This function queries a dictionary for a key. A key as read from an
362   ini file is given as "section:key". If the key cannot be found,
363   the pointer passed as 'def' is returned.
364   The returned char pointer is pointing to a string allocated in
365   the dictionary, do not free or modify it.
366  */
367 /*--------------------------------------------------------------------------*/
iniparser_getstring(dictionary * d,const char * key,char * def)368 char * iniparser_getstring(dictionary * d, const char * key, char * def)
369 {
370     char * lc_key ;
371     char * sval ;
372 
373     if (d==NULL || key==NULL)
374         return def ;
375 
376     lc_key = strlwc(key);
377     sval = dictionary_get(d, lc_key, def);
378     return sval ;
379 }
380 
381 /*-------------------------------------------------------------------------*/
382 /**
383   @brief    Get the string associated to a key, convert to an int
384   @param    d Dictionary to search
385   @param    key Key string to look for
386   @param    notfound Value to return in case of error
387   @return   integer
388 
389   This function queries a dictionary for a key. A key as read from an
390   ini file is given as "section:key". If the key cannot be found,
391   the notfound value is returned.
392 
393   Supported values for integers include the usual C notation
394   so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
395   are supported. Examples:
396 
397   "42"      ->  42
398   "042"     ->  34 (octal -> decimal)
399   "0x42"    ->  66 (hexa  -> decimal)
400 
401   Warning: the conversion may overflow in various ways. Conversion is
402   totally outsourced to strtol(), see the associated man page for overflow
403   handling.
404 
405   Credits: Thanks to A. Becker for suggesting strtol()
406  */
407 /*--------------------------------------------------------------------------*/
iniparser_getint(dictionary * d,const char * key,int notfound)408 int iniparser_getint(dictionary * d, const char * key, int notfound)
409 {
410     char    *   str ;
411 
412     str = iniparser_getstring(d, key, INI_INVALID_KEY);
413     if (str==INI_INVALID_KEY) return notfound ;
414     return (int)strtol(str, NULL, 0);
415 }
416 
417 /*-------------------------------------------------------------------------*/
418 /**
419   @brief    Get the string associated to a key, convert to a double
420   @param    d Dictionary to search
421   @param    key Key string to look for
422   @param    notfound Value to return in case of error
423   @return   double
424 
425   This function queries a dictionary for a key. A key as read from an
426   ini file is given as "section:key". If the key cannot be found,
427   the notfound value is returned.
428  */
429 /*--------------------------------------------------------------------------*/
iniparser_getdouble(dictionary * d,const char * key,double notfound)430 double iniparser_getdouble(dictionary * d, const char * key, double notfound)
431 {
432     char    *   str ;
433 
434     str = iniparser_getstring(d, key, INI_INVALID_KEY);
435     if (str==INI_INVALID_KEY) return notfound ;
436     return atof(str);
437 }
438 
439 /*-------------------------------------------------------------------------*/
440 /**
441   @brief    Get the string associated to a key, convert to a boolean
442   @param    d Dictionary to search
443   @param    key Key string to look for
444   @param    notfound Value to return in case of error
445   @return   integer
446 
447   This function queries a dictionary for a key. A key as read from an
448   ini file is given as "section:key". If the key cannot be found,
449   the notfound value is returned.
450 
451   A true boolean is found if one of the following is matched:
452 
453   - A string starting with 'y'
454   - A string starting with 'Y'
455   - A string starting with 't'
456   - A string starting with 'T'
457   - A string starting with '1'
458 
459   A false boolean is found if one of the following is matched:
460 
461   - A string starting with 'n'
462   - A string starting with 'N'
463   - A string starting with 'f'
464   - A string starting with 'F'
465   - A string starting with '0'
466 
467   The notfound value returned if no boolean is identified, does not
468   necessarily have to be 0 or 1.
469  */
470 /*--------------------------------------------------------------------------*/
iniparser_getboolean(dictionary * d,const char * key,int notfound)471 int iniparser_getboolean(dictionary * d, const char * key, int notfound)
472 {
473     char    *   c ;
474     int         ret ;
475 
476     c = iniparser_getstring(d, key, INI_INVALID_KEY);
477     if (c==INI_INVALID_KEY) return notfound ;
478     if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') {
479         ret = 1 ;
480     } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') {
481         ret = 0 ;
482     } else {
483         ret = notfound ;
484     }
485     return ret;
486 }
487 
488 /*-------------------------------------------------------------------------*/
489 /**
490   @brief    Finds out if a given entry exists in a dictionary
491   @param    ini     Dictionary to search
492   @param    entry   Name of the entry to look for
493   @return   integer 1 if entry exists, 0 otherwise
494 
495   Finds out if a given entry exists in the dictionary. Since sections
496   are stored as keys with NULL associated values, this is the only way
497   of querying for the presence of sections in a dictionary.
498  */
499 /*--------------------------------------------------------------------------*/
iniparser_find_entry(dictionary * ini,const char * entry)500 int iniparser_find_entry(
501     dictionary  *   ini,
502     const char  *   entry
503 )
504 {
505     int found=0 ;
506     if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) {
507         found = 1 ;
508     }
509     return found ;
510 }
511 
512 /*-------------------------------------------------------------------------*/
513 /**
514   @brief    Set an entry in a dictionary.
515   @param    ini     Dictionary to modify.
516   @param    entry   Entry to modify (entry name)
517   @param    val     New value to associate to the entry.
518   @return   int 0 if Ok, -1 otherwise.
519 
520   If the given entry can be found in the dictionary, it is modified to
521   contain the provided value. If it cannot be found, -1 is returned.
522   It is Ok to set val to NULL.
523  */
524 /*--------------------------------------------------------------------------*/
iniparser_set(dictionary * ini,const char * entry,const char * val)525 int iniparser_set(dictionary * ini, const char * entry, const char * val)
526 {
527     return dictionary_set(ini, strlwc(entry), val) ;
528 }
529 
530 /*-------------------------------------------------------------------------*/
531 /**
532   @brief    Delete an entry in a dictionary
533   @param    ini     Dictionary to modify
534   @param    entry   Entry to delete (entry name)
535   @return   void
536 
537   If the given entry can be found, it is deleted from the dictionary.
538  */
539 /*--------------------------------------------------------------------------*/
iniparser_unset(dictionary * ini,const char * entry)540 void iniparser_unset(dictionary * ini, const char * entry)
541 {
542     dictionary_unset(ini, strlwc(entry));
543 }
544 
545 /*-------------------------------------------------------------------------*/
546 /**
547   @brief    Load a single line from an INI file
548   @param    input_line  Input line, may be concatenated multi-line input
549   @param    section     Output space to store section
550   @param    key         Output space to store key
551   @param    value       Output space to store value
552   @return   line_status value
553  */
554 /*--------------------------------------------------------------------------*/
iniparser_line(const char * input_line,char * section,char * key,char * value)555 static line_status iniparser_line(
556     const char * input_line,
557     char * section,
558     char * key,
559     char * value)
560 {
561     line_status sta ;
562     char        line[ASCIILINESZ+1];
563     int         len ;
564 
565     strcpy(line, strstrip(input_line));
566     len = (int)strlen(line);
567 
568     sta = LINE_UNPROCESSED ;
569     if (len<1) {
570         /* Empty line */
571         sta = LINE_EMPTY ;
572     } else if (line[0]=='#' || line[0]==';') {
573         /* Comment line */
574         sta = LINE_COMMENT ;
575     } else if (line[0]=='[' && line[len-1]==']') {
576         /* Section name */
577         sscanf(line, "[%[^]]", section);
578         strcpy(section, strstrip(section));
579         strcpy(section, strlwc(section));
580         sta = LINE_SECTION ;
581     } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
582            ||  sscanf (line, "%[^=] = '%[^\']'",   key, value) == 2
583            ||  sscanf (line, "%[^=] = %[^;#]",     key, value) == 2) {
584         /* Usual key=value, with or without comments */
585         strcpy(key, strstrip(key));
586         strcpy(key, strlwc(key));
587         strcpy(value, strstrip(value));
588         /*
589          * sscanf cannot handle '' or "" as empty values
590          * this is done here
591          */
592         if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
593             value[0]=0 ;
594         }
595         sta = LINE_VALUE ;
596     } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2
597            ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
598         /*
599          * Special cases:
600          * key=
601          * key=;
602          * key=#
603          */
604         strcpy(key, strstrip(key));
605         strcpy(key, strlwc(key));
606         value[0]=0 ;
607         sta = LINE_VALUE ;
608     } else {
609         /* Generate syntax error */
610         sta = LINE_ERROR ;
611     }
612     return sta ;
613 }
614 
615 /*-------------------------------------------------------------------------*/
616 /**
617   @brief    Parse an ini file and return an allocated dictionary object
618   @param    ininame Name of the ini file to read.
619   @return   Pointer to newly allocated dictionary
620 
621   This is the parser for ini files. This function is called, providing
622   the name of the file to be read. It returns a dictionary object that
623   should not be accessed directly, but through accessor functions
624   instead.
625 
626   The returned dictionary must be freed using iniparser_freedict().
627  */
628 /*--------------------------------------------------------------------------*/
iniparser_load(const char * ininame)629 dictionary * iniparser_load(const char * ininame)
630 {
631     FILE * in ;
632 
633     char line    [ASCIILINESZ+1] ;
634     char section [ASCIILINESZ+1] ;
635     char key     [ASCIILINESZ+1] ;
636     char tmp     [ASCIILINESZ+1] ;
637     char val     [ASCIILINESZ+1] ;
638 
639     int  last=0 ;
640     int  len ;
641     int  lineno=0 ;
642     int  errs=0;
643 
644     dictionary * dict ;
645 
646     if ((in=fopen(ininame, "r"))==NULL) {
647         fprintf(stderr, "iniparser: cannot open %s\n", ininame);
648         return NULL ;
649     }
650 
651     dict = dictionary_new(0) ;
652     if (!dict) {
653         fclose(in);
654         return NULL ;
655     }
656 
657     memset(line,    0, ASCIILINESZ);
658     memset(section, 0, ASCIILINESZ);
659     memset(key,     0, ASCIILINESZ);
660     memset(val,     0, ASCIILINESZ);
661     last=0 ;
662 
663     while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
664         lineno++ ;
665         len = (int)strlen(line)-1;
666         if (len==0)
667             continue;
668         /* Safety check against buffer overflows */
669         if (line[len]!='\n') {
670             fprintf(stderr,
671                     "iniparser: input line too long in %s (%d)\n",
672                     ininame,
673                     lineno);
674             dictionary_del(dict);
675             fclose(in);
676             return NULL ;
677         }
678         /* Get rid of \n and spaces at end of line */
679         while ((len>=0) &&
680                 ((line[len]=='\n') || (isspace(line[len])))) {
681             line[len]=0 ;
682             len-- ;
683         }
684         /* Detect multi-line */
685         if (line[len]=='\\') {
686             /* Multi-line value */
687             last=len ;
688             continue ;
689         } else {
690             last=0 ;
691         }
692         switch (iniparser_line(line, section, key, val)) {
693             case LINE_EMPTY:
694             case LINE_COMMENT:
695             break ;
696 
697             case LINE_SECTION:
698             errs = dictionary_set(dict, section, NULL);
699             break ;
700 
701             case LINE_VALUE:
702             sprintf(tmp, "%s:%s", section, key);
703             errs = dictionary_set(dict, tmp, val) ;
704             break ;
705 
706             case LINE_ERROR:
707             fprintf(stderr, "iniparser: syntax error in %s (%d):\n",
708                     ininame,
709                     lineno);
710             fprintf(stderr, "-> %s\n", line);
711             errs++ ;
712             break;
713 
714             default:
715             break ;
716         }
717         memset(line, 0, ASCIILINESZ);
718         last=0;
719         if (errs<0) {
720             fprintf(stderr, "iniparser: memory allocation failure\n");
721             break ;
722         }
723     }
724     if (errs) {
725         dictionary_del(dict);
726         dict = NULL ;
727     }
728     fclose(in);
729     return dict ;
730 }
731 
732 /*-------------------------------------------------------------------------*/
733 /**
734   @brief    Free all memory associated to an ini dictionary
735   @param    d Dictionary to free
736   @return   void
737 
738   Free all memory associated to an ini dictionary.
739   It is mandatory to call this function before the dictionary object
740   gets out of the current context.
741  */
742 /*--------------------------------------------------------------------------*/
iniparser_freedict(dictionary * d)743 void iniparser_freedict(dictionary * d)
744 {
745     dictionary_del(d);
746 }
747 
748 /* vim: set ts=4 et sw=4 tw=75 */
749