/* $XFree86: xc/programs/Xserver/hw/xfree86/parser/scan.c,v 1.24 2003/01/04 20:20:23 paulo Exp $ */ /* * * Copyright (c) 1997 Metro Link Incorporated * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Except as contained in this notice, the name of the Metro Link shall not be * used in advertising or otherwise to promote the sale, use or other dealings * in this Software without prior written authorization from Metro Link. * */ /* View/edit this file with tab stops set to 4 */ #include #include #include #include #include #include #if !defined(X_NOT_POSIX) #if defined(_POSIX_SOURCE) #include #else #define _POSIX_SOURCE #include #undef _POSIX_SOURCE #endif /* _POSIX_SOURCE */ #endif /* !X_NOT_POSIX */ #if !defined(PATH_MAX) #if defined(MAXPATHLEN) #define PATH_MAX MAXPATHLEN #else #define PATH_MAX 1024 #endif /* MAXPATHLEN */ #endif /* !PATH_MAX */ #if !defined(MAXHOSTNAMELEN) #define MAXHOSTNAMELEN 32 #endif /* !MAXHOSTNAMELEN */ #include "Configint.h" #include "xf86tokens.h" #define CONFIG_BUF_LEN 1024 static int StringToToken (char *, xf86ConfigSymTabRec *); static FILE *configFile = NULL; static int configStart = 0; /* start of the current token */ static int configPos = 0; /* current readers position */ static int configLineNo = 0; /* linenumber */ static char *configBuf, *configRBuf; /* buffer for lines */ static char *configPath; /* path to config file */ static char *configSection = NULL; /* name of current section being parsed */ static int pushToken = LOCK_TOKEN; static int eol_seen = 0; /* private state to handle comments */ LexRec val; #ifdef __UNIXOS2__ extern char *__XOS2RedirRoot(char *path); #endif /* * xf86strToUL -- * * A portable, but restricted, version of strtoul(). It only understands * hex, octal, and decimal. But it's good enough for our needs. */ unsigned int xf86strToUL (char *str) { int base = 10; char *p = str; unsigned int tot = 0; if (*p == '0') { p++; if ((*p == 'x') || (*p == 'X')) { p++; base = 16; } else base = 8; } while (*p) { if ((*p >= '0') && (*p <= ((base == 8) ? '7' : '9'))) { tot = tot * base + (*p - '0'); } else if ((base == 16) && (*p >= 'a') && (*p <= 'f')) { tot = tot * base + 10 + (*p - 'a'); } else if ((base == 16) && (*p >= 'A') && (*p <= 'F')) { tot = tot * base + 10 + (*p - 'A'); } else { return (tot); } p++; } return (tot); } /* * xf86getToken -- * Read next Token form the config file. Handle the global variable * pushToken. */ int xf86getToken (xf86ConfigSymTabRec * tab) { int c, i; /* * First check whether pushToken has a different value than LOCK_TOKEN. * In this case rBuf[] contains a valid STRING/TOKEN/NUMBER. But in the * oth * case the next token must be read from the input. */ if (pushToken == EOF_TOKEN) return (EOF_TOKEN); else if (pushToken == LOCK_TOKEN) { /* * eol_seen is only set for the first token after a newline. */ eol_seen = 0; c = configBuf[configPos]; /* * Get start of next Token. EOF is handled, * whitespaces are skipped. */ again: if (!c) { if (fgets (configBuf, CONFIG_BUF_LEN - 1, configFile) == NULL) { return (pushToken = EOF_TOKEN); } configLineNo++; configStart = configPos = 0; eol_seen = 1; } i = 0; for (;;) { c = configBuf[configPos++]; configRBuf[i++] = c; switch (c) { case ' ': case '\t': case '\r': continue; case '\n': i = 0; continue; } break; } if (c == '\0') goto again; if (c == '#') { do { configRBuf[i++] = (c = configBuf[configPos++]); } while ((c != '\n') && (c != '\r') && (c != '\0')); configRBuf[i] = '\0'; /* XXX no private copy. * Use xf86addComment when setting a comment. */ val.str = configRBuf; return (COMMENT); } /* GJA -- handle '-' and ',' * Be careful: "-hsync" is a keyword. */ else if ((c == ',') && !isalpha (configBuf[configPos])) { configStart = configPos; return COMMA; } else if ((c == '-') && !isalpha (configBuf[configPos])) { configStart = configPos; return DASH; } configStart = configPos; /* * Numbers are returned immediately ... */ if (isdigit (c)) { int base; if (c == '0') if ((configBuf[configPos] == 'x') || (configBuf[configPos] == 'X')) base = 16; else base = 8; else base = 10; configRBuf[0] = c; i = 1; while (isdigit (c = configBuf[configPos++]) || (c == '.') || (c == 'x') || (c == 'X') || ((base == 16) && (((c >= 'a') && (c <= 'f')) || ((c >= 'A') && (c <= 'F'))))) configRBuf[i++] = c; configPos--; /* GJA -- one too far */ configRBuf[i] = '\0'; val.num = xf86strToUL (configRBuf); val.realnum = atof (configRBuf); return (NUMBER); } /* * All Strings START with a \" ... */ else if (c == '\"') { i = -1; do { configRBuf[++i] = (c = configBuf[configPos++]); } while ((c != '\"') && (c != '\n') && (c != '\r') && (c != '\0')); configRBuf[i] = '\0'; val.str = xf86confmalloc (strlen (configRBuf) + 1); strcpy (val.str, configRBuf); /* private copy ! */ return (STRING); } /* * ... and now we MUST have a valid token. The search is * handled later along with the pushed tokens. */ else { configRBuf[0] = c; i = 0; do { configRBuf[++i] = (c = configBuf[configPos++]);; } while ((c != ' ') && (c != '\t') && (c != '\n') && (c != '\r') && (c != '\0') && (c != '#')); --configPos; configRBuf[i] = '\0'; i = 0; } } else { /* * Here we deal with pushed tokens. Reinitialize pushToken again. If * the pushed token was NUMBER || STRING return them again ... */ int temp = pushToken; pushToken = LOCK_TOKEN; if (temp == COMMA || temp == DASH) return (temp); if (temp == NUMBER || temp == STRING) return (temp); } /* * Joop, at last we have to lookup the token ... */ if (tab) { i = 0; while (tab[i].token != -1) if (xf86nameCompare (configRBuf, tab[i].name) == 0) return (tab[i].token); else i++; } return (ERROR_TOKEN); /* Error catcher */ } int xf86getSubToken (char **comment) { int token; for (;;) { token = xf86getToken(NULL); if (token == COMMENT) { if (comment) *comment = xf86addComment(*comment, val.str); } else return (token); } /*NOTREACHED*/ } int xf86getSubTokenWithTab (char **comment, xf86ConfigSymTabRec *tab) { int token; for (;;) { token = xf86getToken(tab); if (token == COMMENT) { if (comment) *comment = xf86addComment(*comment, val.str); } else return (token); } /*NOTREACHED*/ } void xf86unGetToken (int token) { pushToken = token; } char * xf86tokenString (void) { return configRBuf; } #if 1 int xf86pathIsAbsolute(const char *path) { if (path && path[0] == '/') return 1; #ifdef __UNIXOS2__ if (path && (path[0] == '\\' || (path[1] == ':'))) return 0; #endif return 0; } /* A path is "safe" if it is relative and if it contains no ".." elements. */ int xf86pathIsSafe(const char *path) { if (xf86pathIsAbsolute(path)) return 0; /* Compare with ".." */ if (!strcmp(path, "..")) return 0; /* Look for leading "../" */ if (!strncmp(path, "../", 3)) return 0; /* Look for trailing "/.." */ if ((strlen(path) > 3) && !strcmp(path + strlen(path) - 3, "/..")) return 0; /* Look for "/../" */ if (strstr(path, "/../")) return 0; return 1; } /* * This function substitutes the following escape sequences: * * %A cmdline argument as an absolute path (must be absolute to match) * %R cmdline argument as a relative path * %S cmdline argument as a "safe" path (relative, and no ".." elements) * %X default config file name ("XF86Config") * %H hostname * %E config file environment ($XF86CONFIG) as an absolute path * %F config file environment ($XF86CONFIG) as a relative path * %G config file environment ($XF86CONFIG) as a safe path * %D $HOME * %P projroot * %M major version number * %% % * %& UNIXOS2 only: prepend X11ROOT env var */ #ifndef XCONFIGFILE #define XCONFIGFILE "XF86Config" #endif #ifndef PROJECTROOT #define PROJECTROOT "/usr/X11R6" #endif #ifndef XCONFENV #define XCONFENV "XF86CONFIG" #endif #ifndef XF86_VERSION_MAJOR #ifdef XVERSION #if XVERSION > 40000000 #define XF86_VERSION_MAJOR (XVERSION / 10000000) #else #define XF86_VERSION_MAJOR (XVERSION / 1000) #endif #else #define XF86_VERSION_MAJOR 4 #endif #endif #define BAIL_OUT do { \ xf86conffree(result); \ return NULL; \ } while (0) #define CHECK_LENGTH do { \ if (l > PATH_MAX) { \ BAIL_OUT; \ } \ } while (0) #define APPEND_STR(s) do { \ if (strlen(s) + l > PATH_MAX) { \ BAIL_OUT; \ } else { \ strcpy(result + l, s); \ l += strlen(s); \ } \ } while (0) static char * DoSubstitution(const char *template, const char *cmdline, const char *projroot, int *cmdlineUsed, int *envUsed) { char *result; int i, l; static const char *env = NULL, *home = NULL; static char *hostname = NULL; static char majorvers[3] = ""; #ifdef __UNIXOS2__ static char *x11root = NULL; #endif if (!template) return NULL; if (cmdlineUsed) *cmdlineUsed = 0; if (envUsed) *envUsed = 0; result = xf86confmalloc(PATH_MAX + 1); l = 0; for (i = 0; template[i]; i++) { if (template[i] != '%') { result[l++] = template[i]; CHECK_LENGTH; } else { switch (template[++i]) { case 'A': if (cmdline && xf86pathIsAbsolute(cmdline)) { APPEND_STR(cmdline); if (cmdlineUsed) *cmdlineUsed = 1; } else BAIL_OUT; break; case 'R': if (cmdline && !xf86pathIsAbsolute(cmdline)) { APPEND_STR(cmdline); if (cmdlineUsed) *cmdlineUsed = 1; } else BAIL_OUT; break; case 'S': if (cmdline && xf86pathIsSafe(cmdline)) { APPEND_STR(cmdline); if (cmdlineUsed) *cmdlineUsed = 1; } else BAIL_OUT; break; case 'X': APPEND_STR(XCONFIGFILE); break; case 'H': if (!hostname) { if ((hostname = xf86confmalloc(MAXHOSTNAMELEN + 1))) { if (gethostname(hostname, MAXHOSTNAMELEN) == 0) { hostname[MAXHOSTNAMELEN] = '\0'; } else { xf86conffree(hostname); hostname = NULL; } } } if (hostname) APPEND_STR(hostname); break; case 'E': if (!env) env = getenv(XCONFENV); if (env && xf86pathIsAbsolute(env)) { APPEND_STR(env); if (envUsed) *envUsed = 1; } else BAIL_OUT; break; case 'F': if (!env) env = getenv(XCONFENV); if (env && !xf86pathIsAbsolute(env)) { APPEND_STR(env); if (envUsed) *envUsed = 1; } else BAIL_OUT; break; case 'G': if (!env) env = getenv(XCONFENV); if (env && xf86pathIsSafe(env)) { APPEND_STR(env); if (envUsed) *envUsed = 1; } else BAIL_OUT; break; case 'D': if (!home) home = getenv("HOME"); if (home && xf86pathIsAbsolute(home)) APPEND_STR(home); else BAIL_OUT; break; case 'P': if (projroot && xf86pathIsAbsolute(projroot)) APPEND_STR(projroot); else BAIL_OUT; break; case 'M': if (!majorvers[0]) { if (XF86_VERSION_MAJOR < 0 || XF86_VERSION_MAJOR > 99) { fprintf(stderr, "XF86_VERSION_MAJOR is out of range\n"); BAIL_OUT; } else sprintf(majorvers, "%d", XF86_VERSION_MAJOR); } APPEND_STR(majorvers); break; case '%': result[l++] = '%'; CHECK_LENGTH; break; #ifdef __UNIXOS2__ case '&': if (!x11root) x11root = getenv("X11ROOT"); if (x11root) APPEND_STR(x11root); else BAIL_OUT; break; #endif default: fprintf(stderr, "invalid escape %%%c found in path template\n", template[i]); BAIL_OUT; break; } } } #ifdef DEBUG fprintf(stderr, "Converted `%s' to `%s'\n", template, result); #endif return result; } /* * xf86openConfigFile -- * * This function take a config file search path (optional), a command-line * specified file name (optional) and the ProjectRoot path (optional) and * locates and opens a config file based on that information. If a * command-line file name is specified, then this function fails if none * of the located files. * * The return value is a pointer to the actual name of the file that was * opened. When no file is found, the return value is NULL. * * The escape sequences allowed in the search path are defined above. * */ #ifndef DEFAULT_CONF_PATH #define DEFAULT_CONF_PATH "/etc/X11/%S," \ "%P/etc/X11/%S," \ "/etc/X11/%G," \ "%P/etc/X11/%G," \ "/etc/X11/%X-%M," \ "/etc/X11/%X," \ "/etc/%X," \ "%P/etc/X11/%X.%H," \ "%P/etc/X11/%X-%M," \ "%P/etc/X11/%X," \ "%P/lib/X11/%X.%H," \ "%P/lib/X11/%X-%M," \ "%P/lib/X11/%X" #endif const char * xf86openConfigFile(const char *path, const char *cmdline, const char *projroot) { char *pathcopy; const char *template; int cmdlineUsed = 0; configFile = NULL; configStart = 0; /* start of the current token */ configPos = 0; /* current readers position */ configLineNo = 0; /* linenumber */ pushToken = LOCK_TOKEN; if (!path || !path[0]) path = DEFAULT_CONF_PATH; pathcopy = xf86confmalloc(strlen(path) + 1); strcpy(pathcopy, path); if (!projroot || !projroot[0]) projroot = PROJECTROOT; template = strtok(pathcopy, ","); /* First, search for a config file. */ while (template && !configFile) { if ((configPath = DoSubstitution(template, cmdline, projroot, &cmdlineUsed, NULL))) { if ((configFile = fopen(configPath, "r")) != 0) { if (cmdline && !cmdlineUsed) { fclose(configFile); configFile = NULL; } } } if (configPath && !configFile) { xf86conffree(configPath); configPath = NULL; } template = strtok(NULL, ","); } xf86conffree(pathcopy); if (!configFile) { return NULL; } configBuf = xf86confmalloc (CONFIG_BUF_LEN); configRBuf = xf86confmalloc (CONFIG_BUF_LEN); configBuf[0] = '\0'; /* sanity ... */ return configPath; } #else /* * xf86openConfigFile -- * * Formerly findConfigFile(). This function take a pointer to a location * in which to place the actual name of the file that was opened. * This function uses the global character array xf86ConfigFile * This function returns the following results. * * 0 unable to open the config file * 1 file opened and ready to read * */ int xf86openConfigFile (char *filename) { #define MAXPTRIES 6 char *home = NULL; char *xconfig = NULL; char *xwinhome = NULL; char *configPaths[MAXPTRIES]; int pcount = 0, idx; /* * First open if necessary the config file. * If the -xf86config flag was used, use the name supplied there (root only). * If $XF86CONFIG is a pathname, use it as the name of the config file (root) * If $XF86CONFIG is set but doesn't contain a '/', append it to 'XF86Config' * and search the standard places (root only). * If $XF86CONFIG is not set, just search the standard places. */ configFile = NULL; configStart = 0; /* start of the current token */ configPos = 0; /* current readers position */ configLineNo = 0; /* linenumber */ pushToken = LOCK_TOKEN; while (!configFile) { /* * configPaths[0] is used as a buffer for -xf86config * and $XF86CONFIG if it contains a path * configPaths[1...MAXPTRIES-1] is used to store the paths of each of * the other attempts */ for (pcount = idx = 0; idx < MAXPTRIES; idx++) configPaths[idx] = NULL; /* * First check if the -xf86config option was used. */ configPaths[pcount] = xf86confmalloc (PATH_MAX); if (xf86ConfigFile[0]) { strcpy (configPaths[pcount], xf86ConfigFile); if ((configFile = fopen (configPaths[pcount], "r")) != 0) break; else return 0; } /* * Check if XF86CONFIG is set. */ #ifndef __UNIXOS2__ if (getuid () == 0 && (xconfig = getenv ("XF86CONFIG")) != 0 && strchr (xconfig, '/')) #else /* no root available, and filenames start with drive letter */ if ((xconfig = getenv ("XF86CONFIG")) != 0 && isalpha (xconfig[0]) && xconfig[1] == ':') #endif { strcpy (configPaths[pcount], xconfig); if ((configFile = fopen (configPaths[pcount], "r")) != 0) break; else return 0; } #ifndef __UNIXOS2__ /* * ~/XF86Config ... */ if (getuid () == 0 && (home = getenv ("HOME")) != NULL) { configPaths[++pcount] = xf86confmalloc (PATH_MAX); strcpy (configPaths[pcount], home); strcat (configPaths[pcount], "/" XCONFIGFILE); if (xconfig) strcat (configPaths[pcount], xconfig); if ((configFile = fopen (configPaths[pcount], "r")) != 0) break; } /* * /etc/XF86Config */ configPaths[++pcount] = xf86confmalloc (PATH_MAX); strcpy (configPaths[pcount], "/etc/" XCONFIGFILE); if (xconfig) strcat (configPaths[pcount], xconfig); if ((configFile = fopen (configPaths[pcount], "r")) != 0) break; /* * $(XCONFIGDIR)/XF86Config. */ configPaths[++pcount] = xf86confmalloc (PATH_MAX); if (getuid () == 0 && (xwinhome = getenv ("XWINHOME")) != NULL) sprintf (configPaths[pcount], "%s/lib/X11/" XCONFIGFILE, xwinhome); else strcpy (configPaths[pcount], XCONFIGDIR "/" XCONFIGFILE); if (getuid () == 0 && xconfig) strcat (configPaths[pcount], xconfig); strcat (configPaths[pcount], "."); gethostname (configPaths[pcount] + strlen (configPaths[pcount]), MAXHOSTNAMELEN); if ((configFile = fopen (configPaths[pcount], "r")) != 0) break; #endif /* !__UNIXOS2__ */ /* * $(XCONFIGDIR)/XF86Config */ configPaths[++pcount] = xf86confmalloc (PATH_MAX); #ifndef __UNIXOS2__ if (getuid () == 0 && xwinhome) sprintf (configPaths[pcount], "%s/lib/X11/" XCONFIGFILE, xwinhome); else strcpy (configPaths[pcount], XCONFIGDIR "/" XCONFIGFILE); if (getuid () == 0 && xconfig) strcat (configPaths[pcount], xconfig); #else /* * we explicitly forbid numerous config files everywhere for OS/2; * users should consider them lucky to have one in a standard place * and another one with the -xf86config option */ xwinhome = getenv ("X11ROOT"); /* get drive letter */ if (!xwinhome) { fprintf (stderr,"X11ROOT environment variable not set\n"); exit(2); } strcpy (configPaths[pcount], __XOS2RedirRoot ("/XFree86/lib/X11/XConfig")); #endif if ((configFile = fopen (configPaths[pcount], "r")) != 0) break; return 0; } configBuf = xf86confmalloc (CONFIG_BUF_LEN); configRBuf = xf86confmalloc (CONFIG_BUF_LEN); configPath = xf86confmalloc (PATH_MAX); strcpy (configPath, configPaths[pcount]); if (filename) strcpy (filename, configPaths[pcount]); for (idx = 0; idx <= pcount; idx++) if (configPaths[idx] != NULL) xf86conffree (configPaths[idx]); configBuf[0] = '\0'; /* sanity ... */ return 1; } #endif void xf86closeConfigFile (void) { xf86conffree (configPath); configPath = NULL; xf86conffree (configRBuf); configRBuf = NULL; xf86conffree (configBuf); configBuf = NULL; fclose (configFile); configFile = NULL; } void xf86parseError (char *format,...) { va_list ap; #if 0 fprintf (stderr, "Parse error on line %d of section %s in file %s\n", configLineNo, configSection, configPath); fprintf (stderr, "\t"); va_start (ap, format); vfprintf (stderr, format, ap); va_end (ap); fprintf (stderr, "\n"); #else ErrorF ("Parse error on line %d of section %s in file %s\n\t", configLineNo, configSection, configPath); va_start (ap, format); VErrorF (format, ap); va_end (ap); ErrorF ("\n"); #endif } void xf86parseWarning (char *format,...) { va_list ap; #if 0 fprintf (stderr, "Parse warning on line %d of section %s in file %s\n", configLineNo, configSection, configPath); fprintf (stderr, "\t"); va_start (ap, format); vfprintf (stderr, format, ap); va_end (ap); fprintf (stderr, "\n"); #else ErrorF ("Parse warning on line %d of section %s in file %s\n\t", configLineNo, configSection, configPath); va_start (ap, format); VErrorF (format, ap); va_end (ap); ErrorF ("\n"); #endif } void xf86validationError (char *format,...) { va_list ap; #if 0 fprintf (stderr, "Data incomplete in file %s\n", configPath); fprintf (stderr, "\t"); va_start (ap, format); vfprintf (stderr, format, ap); va_end (ap); fprintf (stderr, "\n"); #else ErrorF ("Data incomplete in file %s\n\t", configPath); va_start (ap, format); VErrorF (format, ap); va_end (ap); ErrorF ("\n"); #endif } void xf86setSection (char *section) { if (configSection) xf86conffree(configSection); configSection = xf86confmalloc(strlen (section) + 1); strcpy (configSection, section); } /* * xf86getToken -- * Lookup a string if it is actually a token in disguise. */ int xf86getStringToken (xf86ConfigSymTabRec * tab) { return StringToToken (val.str, tab); } static int StringToToken (char *str, xf86ConfigSymTabRec * tab) { int i; for (i = 0; tab[i].token != -1; i++) { if (!xf86nameCompare (tab[i].name, str)) return tab[i].token; } return (ERROR_TOKEN); } /* * Compare two names. The characters '_', ' ', and '\t' are ignored * in the comparison. */ int xf86nameCompare (const char *s1, const char *s2) { char c1, c2; if (!s1 || *s1 == 0) { if (!s2 || *s2 == 0) return (0); else return (1); } while (*s1 == '_' || *s1 == ' ' || *s1 == '\t') s1++; while (*s2 == '_' || *s2 == ' ' || *s2 == '\t') s2++; c1 = (isupper (*s1) ? tolower (*s1) : *s1); c2 = (isupper (*s2) ? tolower (*s2) : *s2); while (c1 == c2) { if (c1 == '\0') return (0); s1++; s2++; while (*s1 == '_' || *s1 == ' ' || *s1 == '\t') s1++; while (*s2 == '_' || *s2 == ' ' || *s2 == '\t') s2++; c1 = (isupper (*s1) ? tolower (*s1) : *s1); c2 = (isupper (*s2) ? tolower (*s2) : *s2); } return (c1 - c2); } char * xf86addComment(char *cur, char *add) { char *str; int len, curlen, iscomment, hasnewline = 0, endnewline; if (add == NULL || add[0] == '\0') return (cur); if (cur) { curlen = strlen(cur); if (curlen) hasnewline = cur[curlen - 1] == '\n'; eol_seen = 0; } else curlen = 0; str = add; iscomment = 0; while (*str) { if (*str != ' ' && *str != '\t') break; ++str; } iscomment = (*str == '#'); len = strlen(add); endnewline = add[len - 1] == '\n'; len += 1 + iscomment + (!hasnewline) + (!endnewline) + eol_seen; if ((str = xf86confrealloc(cur, len + curlen)) == NULL) return (cur); cur = str; if (eol_seen || (curlen && !hasnewline)) cur[curlen++] = '\n'; if (!iscomment) cur[curlen++] = '#'; strcpy(cur + curlen, add); if (!endnewline) strcat(cur, "\n"); return (cur); }