| @@ -1,733 +1,921 @@ | | | @@ -1,733 +1,921 @@ |
1 | /* | | 1 | /* |
2 | * Copyright (C) 1986-2005 The Free Software Foundation, Inc. | | 2 | * Copyright (C) 1986-2005 The Free Software Foundation, Inc. |
3 | * | | 3 | * |
4 | * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, | | 4 | * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, |
5 | * and others. | | 5 | * and others. |
6 | * | | 6 | * |
7 | * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk | | 7 | * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk |
8 | * Portions Copyright (C) 1989-1992, Brian Berliner | | 8 | * Portions Copyright (C) 1989-1992, Brian Berliner |
9 | * | | 9 | * |
10 | * You may distribute under the terms of the GNU General Public License as | | 10 | * You may distribute under the terms of the GNU General Public License as |
11 | * specified in the README file that comes with the CVS source distribution. | | 11 | * specified in the README file that comes with the CVS source distribution. |
12 | */ | | 12 | */ |
13 | | | 13 | |
14 | #include "cvs.h" | | 14 | #include "cvs.h" |
15 | #include "getline.h" | | 15 | #include "getline.h" |
16 | #include "history.h" | | 16 | #include "history.h" |
17 | | | 17 | |
18 | /* | | 18 | /* |
19 | * Parse the INFOFILE file for the specified REPOSITORY. Invoke CALLPROC for | | 19 | * Parse the INFOFILE file for the specified REPOSITORY. Invoke CALLPROC for |
20 | * the first line in the file that matches the REPOSITORY, or if ALL != 0, any | | 20 | * the first line in the file that matches the REPOSITORY, or if ALL != 0, any |
21 | * lines matching "ALL", or if no lines match, the last line matching | | 21 | * lines matching "ALL", or if no lines match, the last line matching |
22 | * "DEFAULT". | | 22 | * "DEFAULT". |
23 | * | | 23 | * |
24 | * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure. | | 24 | * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure. |
25 | */ | | 25 | */ |
26 | int | | 26 | int |
27 | Parse_Info (const char *infofile, const char *repository, CALLPROC callproc, | | 27 | Parse_Info (const char *infofile, const char *repository, CALLPROC callproc, |
28 | int opt, void *closure) | | 28 | int opt, void *closure) |
29 | { | | 29 | { |
30 | int err = 0; | | 30 | int err = 0; |
31 | FILE *fp_info; | | 31 | FILE *fp_info; |
32 | char *infopath; | | 32 | char *infopath; |
33 | char *line = NULL; | | 33 | char *line = NULL; |
34 | size_t line_allocated = 0; | | 34 | size_t line_allocated = 0; |
35 | char *default_value = NULL; | | 35 | char *default_value = NULL; |
36 | int default_line = 0; | | 36 | int default_line = 0; |
37 | char *expanded_value; | | 37 | char *expanded_value; |
38 | bool callback_done; | | 38 | bool callback_done; |
39 | int line_number; | | 39 | int line_number; |
40 | char *cp, *exp, *value; | | 40 | char *cp, *exp, *value; |
41 | const char *srepos; | | 41 | const char *srepos; |
42 | const char *regex_err; | | 42 | const char *regex_err; |
43 | | | 43 | |
44 | assert (repository); | | 44 | assert (repository); |
45 | | | 45 | |
46 | if (!current_parsed_root) | | 46 | if (!current_parsed_root) |
47 | { | | 47 | { |
48 | /* XXX - should be error maybe? */ | | 48 | /* XXX - should be error maybe? */ |
49 | error (0, 0, "CVSROOT variable not set"); | | 49 | error (0, 0, "CVSROOT variable not set"); |
50 | return 1; | | 50 | return 1; |
51 | } | | 51 | } |
52 | | | 52 | |
53 | /* find the info file and open it */ | | 53 | /* find the info file and open it */ |
54 | infopath = Xasprintf ("%s/%s/%s", current_parsed_root->directory, | | 54 | infopath = Xasprintf ("%s/%s/%s", current_parsed_root->directory, |
55 | CVSROOTADM, infofile); | | 55 | CVSROOTADM, infofile); |
56 | fp_info = CVS_FOPEN (infopath, "r"); | | 56 | fp_info = CVS_FOPEN (infopath, "r"); |
57 | if (!fp_info) | | 57 | if (!fp_info) |
58 | { | | 58 | { |
59 | /* If no file, don't do anything special. */ | | 59 | /* If no file, don't do anything special. */ |
60 | if (!existence_error (errno)) | | 60 | if (!existence_error (errno)) |
61 | error (0, errno, "cannot open %s", infopath); | | 61 | error (0, errno, "cannot open %s", infopath); |
62 | free (infopath); | | 62 | free (infopath); |
63 | return 0; | | 63 | return 0; |
64 | } | | 64 | } |
65 | | | 65 | |
66 | /* strip off the CVSROOT if repository was absolute */ | | 66 | /* strip off the CVSROOT if repository was absolute */ |
67 | srepos = Short_Repository (repository); | | 67 | srepos = Short_Repository (repository); |
68 | | | 68 | |
69 | TRACE (TRACE_FUNCTION, "Parse_Info (%s, %s, %s)", | | 69 | TRACE (TRACE_FUNCTION, "Parse_Info (%s, %s, %s)", |
70 | infopath, srepos, (opt & PIOPT_ALL) ? "ALL" : "not ALL"); | | 70 | infopath, srepos, (opt & PIOPT_ALL) ? "ALL" : "not ALL"); |
71 | | | 71 | |
72 | /* search the info file for lines that match */ | | 72 | /* search the info file for lines that match */ |
73 | callback_done = false; | | 73 | callback_done = false; |
74 | line_number = 0; | | 74 | line_number = 0; |
75 | while (getline (&line, &line_allocated, fp_info) >= 0) | | 75 | while (getline (&line, &line_allocated, fp_info) >= 0) |
76 | { | | 76 | { |
77 | line_number++; | | 77 | line_number++; |
78 | | | 78 | |
79 | /* skip lines starting with # */ | | 79 | /* skip lines starting with # */ |
80 | if (line[0] == '#') | | 80 | if (line[0] == '#') |
81 | continue; | | 81 | continue; |
82 | | | 82 | |
83 | /* skip whitespace at beginning of line */ | | 83 | /* skip whitespace at beginning of line */ |
84 | for (cp = line; *cp && isspace ((unsigned char) *cp); cp++) | | 84 | for (cp = line; *cp && isspace ((unsigned char) *cp); cp++) |
85 | ; | | 85 | ; |
86 | | | 86 | |
87 | /* if *cp is null, the whole line was blank */ | | 87 | /* if *cp is null, the whole line was blank */ |
88 | if (*cp == '\0') | | 88 | if (*cp == '\0') |
89 | continue; | | 89 | continue; |
90 | | | 90 | |
91 | /* the regular expression is everything up to the first space */ | | 91 | /* the regular expression is everything up to the first space */ |
92 | for (exp = cp; *cp && !isspace ((unsigned char) *cp); cp++) | | 92 | for (exp = cp; *cp && !isspace ((unsigned char) *cp); cp++) |
93 | ; | | 93 | ; |
94 | if (*cp != '\0') | | 94 | if (*cp != '\0') |
95 | *cp++ = '\0'; | | 95 | *cp++ = '\0'; |
96 | | | 96 | |
97 | /* skip whitespace up to the start of the matching value */ | | 97 | /* skip whitespace up to the start of the matching value */ |
98 | while (*cp && isspace ((unsigned char) *cp)) | | 98 | while (*cp && isspace ((unsigned char) *cp)) |
99 | cp++; | | 99 | cp++; |
100 | | | 100 | |
101 | /* no value to match with the regular expression is an error */ | | 101 | /* no value to match with the regular expression is an error */ |
102 | if (*cp == '\0') | | 102 | if (*cp == '\0') |
103 | { | | 103 | { |
104 | error (0, 0, "syntax error at line %d file %s; ignored", | | 104 | error (0, 0, "syntax error at line %d file %s; ignored", |
105 | line_number, infopath); | | 105 | line_number, infopath); |
106 | continue; | | 106 | continue; |
107 | } | | 107 | } |
108 | value = cp; | | 108 | value = cp; |
109 | | | 109 | |
110 | /* strip the newline off the end of the value */ | | 110 | /* strip the newline off the end of the value */ |
111 | cp = strrchr (value, '\n'); | | 111 | cp = strrchr (value, '\n'); |
112 | if (cp) *cp = '\0'; | | 112 | if (cp) *cp = '\0'; |
113 | | | 113 | |
114 | /* | | 114 | /* |
115 | * At this point, exp points to the regular expression, and value | | 115 | * At this point, exp points to the regular expression, and value |
116 | * points to the value to call the callback routine with. Evaluate | | 116 | * points to the value to call the callback routine with. Evaluate |
117 | * the regular expression against srepos and callback with the value | | 117 | * the regular expression against srepos and callback with the value |
118 | * if it matches. | | 118 | * if it matches. |
119 | */ | | 119 | */ |
120 | | | 120 | |
121 | /* save the default value so we have it later if we need it */ | | 121 | /* save the default value so we have it later if we need it */ |
122 | if (strcmp (exp, "DEFAULT") == 0) | | 122 | if (strcmp (exp, "DEFAULT") == 0) |
123 | { | | 123 | { |
124 | if (default_value) | | 124 | if (default_value) |
125 | { | | 125 | { |
126 | error (0, 0, "Multiple `DEFAULT' lines (%d and %d) in %s file", | | 126 | error (0, 0, "Multiple `DEFAULT' lines (%d and %d) in %s file", |
127 | default_line, line_number, infofile); | | 127 | default_line, line_number, infofile); |
128 | free (default_value); | | 128 | free (default_value); |
129 | } | | 129 | } |
130 | default_value = xstrdup (value); | | 130 | default_value = xstrdup (value); |
131 | default_line = line_number; | | 131 | default_line = line_number; |
132 | continue; | | 132 | continue; |
133 | } | | 133 | } |
134 | | | 134 | |
135 | /* | | 135 | /* |
136 | * For a regular expression of "ALL", do the callback always We may | | 136 | * For a regular expression of "ALL", do the callback always We may |
137 | * execute lots of ALL callbacks in addition to *one* regular matching | | 137 | * execute lots of ALL callbacks in addition to *one* regular matching |
138 | * callback or default | | 138 | * callback or default |
139 | */ | | 139 | */ |
140 | if (strcmp (exp, "ALL") == 0) | | 140 | if (strcmp (exp, "ALL") == 0) |
141 | { | | 141 | { |
142 | if (!(opt & PIOPT_ALL)) | | 142 | if (!(opt & PIOPT_ALL)) |
143 | error (0, 0, "Keyword `ALL' is ignored at line %d in %s file", | | 143 | error (0, 0, "Keyword `ALL' is ignored at line %d in %s file", |
144 | line_number, infofile); | | 144 | line_number, infofile); |
145 | else if ((expanded_value = | | 145 | else if ((expanded_value = |
146 | expand_path (value, current_parsed_root->directory, | | 146 | expand_path (value, current_parsed_root->directory, |
147 | true, infofile, line_number))) | | 147 | true, infofile, line_number))) |
148 | { | | 148 | { |
149 | err += callproc (repository, expanded_value, closure); | | 149 | err += callproc (repository, expanded_value, closure); |
150 | free (expanded_value); | | 150 | free (expanded_value); |
151 | } | | 151 | } |
152 | else | | 152 | else |
153 | err++; | | 153 | err++; |
154 | continue; | | 154 | continue; |
155 | } | | 155 | } |
156 | | | 156 | |
157 | if (callback_done) | | 157 | if (callback_done) |
158 | /* only first matching, plus "ALL"'s */ | | 158 | /* only first matching, plus "ALL"'s */ |
159 | continue; | | 159 | continue; |
160 | | | 160 | |
161 | /* see if the repository matched this regular expression */ | | 161 | /* see if the repository matched this regular expression */ |
162 | regex_err = re_comp (exp); | | 162 | regex_err = re_comp (exp); |
163 | if (regex_err) | | 163 | if (regex_err) |
164 | { | | 164 | { |
165 | error (0, 0, "bad regular expression at line %d file %s: %s", | | 165 | error (0, 0, "bad regular expression at line %d file %s: %s", |
166 | line_number, infofile, regex_err); | | 166 | line_number, infofile, regex_err); |
167 | continue; | | 167 | continue; |
168 | } | | 168 | } |
169 | if (re_exec (srepos) == 0) | | 169 | if (re_exec (srepos) == 0) |
170 | continue; /* no match */ | | 170 | continue; /* no match */ |
171 | | | 171 | |
172 | /* it did, so do the callback and note that we did one */ | | 172 | /* it did, so do the callback and note that we did one */ |
173 | expanded_value = expand_path (value, current_parsed_root->directory, | | 173 | expanded_value = expand_path (value, current_parsed_root->directory, |
174 | true, infofile, line_number); | | 174 | true, infofile, line_number); |
175 | if (expanded_value) | | 175 | if (expanded_value) |
176 | { | | 176 | { |
177 | err += callproc (repository, expanded_value, closure); | | 177 | err += callproc (repository, expanded_value, closure); |
178 | free (expanded_value); | | 178 | free (expanded_value); |
179 | } | | 179 | } |
180 | else | | 180 | else |
181 | err++; | | 181 | err++; |
182 | callback_done = true; | | 182 | callback_done = true; |
183 | } | | 183 | } |
184 | if (ferror (fp_info)) | | 184 | if (ferror (fp_info)) |
185 | error (0, errno, "cannot read %s", infopath); | | 185 | error (0, errno, "cannot read %s", infopath); |
186 | if (fclose (fp_info) < 0) | | 186 | if (fclose (fp_info) < 0) |
187 | error (0, errno, "cannot close %s", infopath); | | 187 | error (0, errno, "cannot close %s", infopath); |
188 | | | 188 | |
189 | /* if we fell through and didn't callback at all, do the default */ | | 189 | /* if we fell through and didn't callback at all, do the default */ |
190 | if (!callback_done && default_value) | | 190 | if (!callback_done && default_value) |
191 | { | | 191 | { |
192 | expanded_value = expand_path (default_value, | | 192 | expanded_value = expand_path (default_value, |
193 | current_parsed_root->directory, | | 193 | current_parsed_root->directory, |
194 | true, infofile, line_number); | | 194 | true, infofile, line_number); |
195 | if (expanded_value) | | 195 | if (expanded_value) |
196 | { | | 196 | { |
197 | err += callproc (repository, expanded_value, closure); | | 197 | err += callproc (repository, expanded_value, closure); |
198 | free (expanded_value); | | 198 | free (expanded_value); |
199 | } | | 199 | } |
200 | else | | 200 | else |
201 | err++; | | 201 | err++; |
202 | } | | 202 | } |
203 | | | 203 | |
204 | /* free up space if necessary */ | | 204 | /* free up space if necessary */ |
205 | if (default_value) free (default_value); | | 205 | if (default_value) free (default_value); |
206 | free (infopath); | | 206 | free (infopath); |
207 | if (line) free (line); | | 207 | if (line) free (line); |
208 | | | 208 | |
209 | return err; | | 209 | return err; |
210 | } | | 210 | } |
211 | | | 211 | |
212 | | | 212 | |
213 | | | 213 | |
214 | /* Print a warning and return false if P doesn't look like a string specifying | | 214 | /* Print a warning and return false if P doesn't look like a string specifying |
215 | * something that can be converted into a size_t. | | 215 | * something that can be converted into a size_t. |
216 | * | | 216 | * |
217 | * Sets *VAL to the parsed value when it is found to be valid. *VAL will not | | 217 | * Sets *VAL to the parsed value when it is found to be valid. *VAL will not |
218 | * be altered when false is returned. | | 218 | * be altered when false is returned. |
219 | */ | | 219 | */ |
220 | static bool | | 220 | static bool |
221 | readSizeT (const char *infopath, const char *option, const char *p, | | 221 | readSizeT (const char *infopath, const char *option, const char *p, |
222 | size_t *val) | | 222 | size_t *val) |
223 | { | | 223 | { |
224 | const char *q; | | 224 | const char *q; |
225 | size_t num, factor = 1; | | 225 | size_t num, factor = 1; |
226 | | | 226 | |
227 | if (!strcasecmp ("unlimited", p)) | | 227 | if (!strcasecmp ("unlimited", p)) |
228 | { | | 228 | { |
229 | *val = SIZE_MAX; | | 229 | *val = SIZE_MAX; |
230 | return true; | | 230 | return true; |
231 | } | | 231 | } |
232 | | | 232 | |
233 | /* Record the factor character (kilo, mega, giga, tera). */ | | 233 | /* Record the factor character (kilo, mega, giga, tera). */ |
234 | if (!isdigit (p[strlen(p) - 1])) | | 234 | if (!isdigit (p[strlen(p) - 1])) |
235 | { | | 235 | { |
236 | switch (p[strlen(p) - 1]) | | 236 | switch (p[strlen(p) - 1]) |
237 | { | | 237 | { |
238 | case 'T': | | 238 | case 'T': |
239 | factor = xtimes (factor, 1024); | | 239 | factor = xtimes (factor, 1024); |
240 | case 'G': | | 240 | case 'G': |
241 | factor = xtimes (factor, 1024); | | 241 | factor = xtimes (factor, 1024); |
242 | case 'M': | | 242 | case 'M': |
243 | factor = xtimes (factor, 1024); | | 243 | factor = xtimes (factor, 1024); |
244 | case 'k': | | 244 | case 'k': |
245 | factor = xtimes (factor, 1024); | | 245 | factor = xtimes (factor, 1024); |
246 | break; | | 246 | break; |
247 | default: | | 247 | default: |
248 | error (0, 0, | | 248 | error (0, 0, |
249 | "%s: Unknown %s factor: `%c'", | | 249 | "%s: Unknown %s factor: `%c'", |
250 | infopath, option, p[strlen(p)]); | | 250 | infopath, option, p[strlen(p)]); |
251 | return false; | | 251 | return false; |
252 | } | | 252 | } |
253 | TRACE (TRACE_DATA, "readSizeT(): Found factor %u for %s", | | 253 | TRACE (TRACE_DATA, "readSizeT(): Found factor %zu for %s", |
254 | factor, option); | | 254 | factor, option); |
255 | } | | 255 | } |
256 | | | 256 | |
257 | /* Verify that *q is a number. */ | | 257 | /* Verify that *q is a number. */ |
258 | q = p; | | 258 | q = p; |
259 | while (q < p + strlen(p) - 1 /* Checked last character above. */) | | 259 | while (q < p + strlen(p) - 1 /* Checked last character above. */) |
260 | { | | 260 | { |
261 | if (!isdigit(*q)) | | 261 | if (!isdigit(*q)) |
262 | { | | 262 | { |
263 | error (0, 0, | | 263 | error (0, 0, |
264 | "%s: %s must be a postitive integer, not '%s'", | | 264 | "%s: %s must be a postitive integer, not '%s'", |
265 | infopath, option, p); | | 265 | infopath, option, p); |
266 | return false; | | 266 | return false; |
267 | } | | 267 | } |
268 | q++; | | 268 | q++; |
269 | } | | 269 | } |
270 | | | 270 | |
271 | /* Compute final value. */ | | 271 | /* Compute final value. */ |
272 | num = strtoul (p, NULL, 10); | | 272 | num = strtoul (p, NULL, 10); |
273 | if (num == ULONG_MAX || num > SIZE_MAX) | | 273 | if (num == ULONG_MAX || num > SIZE_MAX) |
274 | /* Don't return an error, just max out. */ | | 274 | /* Don't return an error, just max out. */ |
275 | num = SIZE_MAX; | | 275 | num = SIZE_MAX; |
276 | | | 276 | |
277 | TRACE (TRACE_DATA, "readSizeT(): read number %u for %s", num, option); | | 277 | TRACE (TRACE_DATA, "readSizeT(): read number %zu for %s", num, option); |
278 | *val = xtimes (strtoul (p, NULL, 10), factor); | | 278 | *val = xtimes (strtoul (p, NULL, 10), factor); |
279 | TRACE (TRACE_DATA, "readSizeT(): returnning %u for %s", *val, option); | | 279 | TRACE (TRACE_DATA, "readSizeT(): returnning %zu for %s", *val, option); |
280 | return true; | | 280 | return true; |
281 | } | | 281 | } |
282 | | | 282 | |
283 | | | 283 | |
284 | | | 284 | |
285 | /* Allocate and initialize a new config struct. */ | | 285 | /* Allocate and initialize a new config struct. */ |
286 | static inline struct config * | | 286 | static inline struct config * |
287 | new_config (void) | | 287 | new_config (void) |
288 | { | | 288 | { |
289 | struct config *new = xcalloc (1, sizeof (struct config)); | | 289 | struct config *new = xcalloc (1, sizeof (struct config)); |
290 | | | 290 | |
291 | TRACE (TRACE_FLOW, "new_config ()"); | | 291 | TRACE (TRACE_FLOW, "new_config ()"); |
292 | | | 292 | |
293 | new->logHistory = xstrdup (ALL_HISTORY_REC_TYPES); | | 293 | new->logHistory = xstrdup (ALL_HISTORY_REC_TYPES); |
294 | new->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; | | 294 | new->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; |
295 | new->UserAdminOptions = xstrdup ("k"); | | 295 | new->UserAdminOptions = xstrdup ("k"); |
296 | #ifdef CVS_ADMIN_GROUP | | 296 | #ifdef CVS_ADMIN_GROUP |
297 | new->UserAdminGroup = xstrdup (CVS_ADMIN_GROUP); | | 297 | new->UserAdminGroup = xstrdup (CVS_ADMIN_GROUP); |
298 | #else | | 298 | #else |
299 | new->UserAdminGroup = NULL; | | 299 | new->UserAdminGroup = NULL; |
300 | #endif | | 300 | #endif |
301 | new->MaxCommentLeaderLength = 20; | | 301 | new->MaxCommentLeaderLength = 20; |
302 | #ifdef SERVER_SUPPORT | | 302 | #ifdef SERVER_SUPPORT |
303 | new->MaxCompressionLevel = 9; | | 303 | new->MaxCompressionLevel = 9; |
304 | #endif /* SERVER_SUPPORT */ | | 304 | #endif /* SERVER_SUPPORT */ |
305 | #ifdef PROXY_SUPPORT | | 305 | #ifdef PROXY_SUPPORT |
306 | new->MaxProxyBufferSize = (size_t)(8 * 1024 * 1024); /* 8 megabytes, | | 306 | new->MaxProxyBufferSize = (size_t)(8 * 1024 * 1024); /* 8 megabytes, |
307 | * by default. | | 307 | * by default. |
308 | */ | | 308 | */ |
309 | #endif /* PROXY_SUPPORT */ | | 309 | #endif /* PROXY_SUPPORT */ |
310 | #ifdef AUTH_SERVER_SUPPORT | | 310 | #ifdef AUTH_SERVER_SUPPORT |
311 | new->system_auth = true; | | 311 | new->system_auth = true; |
312 | #endif /* AUTH_SERVER_SUPPORT */ | | 312 | #endif /* AUTH_SERVER_SUPPORT */ |
313 | | | 313 | |
314 | return new; | | 314 | return new; |
315 | } | | 315 | } |
316 | | | 316 | |
317 | | | 317 | |
318 | | | 318 | |
319 | void | | 319 | void |
320 | free_config (struct config *data) | | 320 | free_config (struct config *data) |
321 | { | | 321 | { |
322 | if (data->keywords) free_keywords (data->keywords); | | 322 | if (data->keywords) free_keywords (data->keywords); |
323 | free (data); | | 323 | free (data); |
324 | } | | 324 | } |
325 | | | 325 | |
326 | | | 326 | |
327 | | | 327 | |
328 | /* Return true if this function has already been called for line LN of file | | 328 | /* Return true if this function has already been called for line LN of file |
329 | * INFOPATH. | | 329 | * INFOPATH. |
330 | */ | | 330 | */ |
331 | bool | | 331 | bool |
332 | parse_error (const char *infopath, unsigned int ln) | | 332 | parse_error (const char *infopath, unsigned int ln) |
333 | { | | 333 | { |
334 | static List *errors = NULL; | | 334 | static List *errors = NULL; |
335 | char *nodename = NULL; | | 335 | char *nodename = NULL; |
336 | | | 336 | |
337 | if (!errors) | | 337 | if (!errors) |
338 | errors = getlist(); | | 338 | errors = getlist(); |
339 | | | 339 | |
340 | nodename = Xasprintf ("%s/%u", infopath, ln); | | 340 | nodename = Xasprintf ("%s/%u", infopath, ln); |
341 | if (findnode (errors, nodename)) | | 341 | if (findnode (errors, nodename)) |
342 | { | | 342 | { |
343 | free (nodename); | | 343 | free (nodename); |
344 | return true; | | 344 | return true; |
345 | } | | 345 | } |
346 | | | 346 | |
347 | push_string (errors, nodename); | | 347 | push_string (errors, nodename); |
348 | return false; | | 348 | return false; |
349 | } | | 349 | } |
350 | | | 350 | |
351 | | | 351 | |
352 | | | 352 | |
353 | #ifdef ALLOW_CONFIG_OVERRIDE | | 353 | #ifdef ALLOW_CONFIG_OVERRIDE |
354 | const char * const allowed_config_prefixes[] = { ALLOW_CONFIG_OVERRIDE }; | | 354 | const char * const allowed_config_prefixes[] = { ALLOW_CONFIG_OVERRIDE }; |
355 | #endif /* ALLOW_CONFIG_OVERRIDE */ | | 355 | #endif /* ALLOW_CONFIG_OVERRIDE */ |
356 | | | 356 | |
357 | | | 357 | |
358 | | | 358 | |
359 | /* Parse the CVS config file. The syntax right now is a bit ad hoc | | 359 | /* Parse the CVS config file. The syntax right now is a bit ad hoc |
360 | * but tries to draw on the best or more common features of the other | | 360 | * but tries to draw on the best or more common features of the other |
361 | * *info files and various unix (or non-unix) config file syntaxes. | | 361 | * *info files and various unix (or non-unix) config file syntaxes. |
362 | * Lines starting with # are comments. Settings are lines of the form | | 362 | * Lines starting with # are comments. Settings are lines of the form |
363 | * KEYWORD=VALUE. There is currently no way to have a multi-line | | 363 | * KEYWORD=VALUE. There is currently no way to have a multi-line |
364 | * VALUE (would be nice if there was, probably). | | 364 | * VALUE (would be nice if there was, probably). |
365 | * | | 365 | * |
366 | * CVSROOT is the $CVSROOT directory | | 366 | * CVSROOT is the $CVSROOT directory |
367 | * (current_parsed_root->directory might not be set yet, so this | | 367 | * (current_parsed_root->directory might not be set yet, so this |
368 | * function takes the cvsroot as a function argument). | | 368 | * function takes the cvsroot as a function argument). |
369 | * | | 369 | * |
370 | * RETURNS | | 370 | * RETURNS |
371 | * Always returns a fully initialized config struct, which on error may | | 371 | * Always returns a fully initialized config struct, which on error may |
372 | * contain only the defaults. | | 372 | * contain only the defaults. |
373 | * | | 373 | * |
374 | * ERRORS | | 374 | * ERRORS |
375 | * Calls error(0, ...) on errors in addition to the return value. | | 375 | * Calls error(0, ...) on errors in addition to the return value. |
376 | * | | 376 | * |
377 | * xmalloc() failures are fatal, per usual. | | 377 | * xmalloc() failures are fatal, per usual. |
378 | */ | | 378 | */ |
379 | struct config * | | 379 | struct config * |
380 | parse_config (const char *cvsroot, const char *path) | | 380 | parse_config (const char *cvsroot, const char *path) |
381 | { | | 381 | { |
382 | const char *infopath; | | 382 | const char *infopath; |
383 | char *freeinfopath = NULL; | | 383 | char *freeinfopath = NULL; |
384 | FILE *fp_info; | | 384 | FILE *fp_info; |
385 | char *line = NULL; | | 385 | char *line = NULL; |
386 | unsigned int ln; /* Input file line counter. */ | | 386 | unsigned int ln; /* Input file line counter. */ |
387 | char *buf = NULL; | | 387 | char *buf = NULL; |
388 | size_t buf_allocated = 0; | | 388 | size_t buf_allocated = 0; |
389 | size_t len; | | 389 | size_t len; |
390 | char *p; | | 390 | char *p; |
391 | struct config *retval; | | 391 | struct config *retval; |
392 | /* PROCESSING Whether config keys are currently being processed for | | 392 | /* PROCESSING Whether config keys are currently being processed for |
393 | * this root. | | 393 | * this root. |
394 | * PROCESSED Whether any keys have been processed for this root. | | 394 | * PROCESSED Whether any keys have been processed for this root. |
395 | * This is initialized to true so that any initial keys | | 395 | * This is initialized to true so that any initial keys |
396 | * may be processed as global defaults. | | 396 | * may be processed as global defaults. |
397 | */ | | 397 | */ |
398 | bool processing = true; | | 398 | bool processing = true; |
399 | bool processed = true; | | 399 | bool processed = true; |
400 | | | 400 | |
401 | TRACE (TRACE_FUNCTION, "parse_config (%s)", cvsroot); | | 401 | TRACE (TRACE_FUNCTION, "parse_config (%s)", cvsroot); |
402 | | | 402 | |
403 | #ifdef ALLOW_CONFIG_OVERRIDE | | 403 | #ifdef ALLOW_CONFIG_OVERRIDE |
404 | if (path) | | 404 | if (path) |
405 | { | | 405 | { |
406 | const char * const *prefix; | | 406 | const char * const *prefix; |
407 | char *npath = xcanonicalize_file_name (path); | | 407 | char *npath = xcanonicalize_file_name (path); |
408 | bool approved = false; | | 408 | bool approved = false; |
409 | for (prefix = allowed_config_prefixes; *prefix != NULL; prefix++) | | 409 | for (prefix = allowed_config_prefixes; *prefix != NULL; prefix++) |
410 | { | | 410 | { |
411 | char *nprefix; | | 411 | char *nprefix; |
412 | | | 412 | |
413 | if (!isreadable (*prefix)) continue; | | 413 | if (!isreadable (*prefix)) continue; |
414 | nprefix = xcanonicalize_file_name (*prefix); | | 414 | nprefix = xcanonicalize_file_name (*prefix); |
415 | if (!strncmp (nprefix, npath, strlen (nprefix)) | | 415 | if (!strncmp (nprefix, npath, strlen (nprefix)) |
416 | && (((*prefix)[strlen (*prefix)] != '/' | | 416 | && (((*prefix)[strlen (*prefix)] != '/' |
417 | && strlen (npath) == strlen (nprefix)) | | 417 | && strlen (npath) == strlen (nprefix)) |
418 | || ((*prefix)[strlen (*prefix)] == '/' | | 418 | || ((*prefix)[strlen (*prefix)] == '/' |
419 | && npath[strlen (nprefix)] == '/'))) | | 419 | && npath[strlen (nprefix)] == '/'))) |
420 | approved = true; | | 420 | approved = true; |
421 | free (nprefix); | | 421 | free (nprefix); |
422 | if (approved) break; | | 422 | if (approved) break; |
423 | } | | 423 | } |
424 | if (!approved) | | 424 | if (!approved) |
425 | error (1, 0, "Invalid path to config file specified: `%s'", | | 425 | error (1, 0, "Invalid path to config file specified: `%s'", |
426 | path); | | 426 | path); |
427 | infopath = path; | | 427 | infopath = path; |
428 | free (npath); | | 428 | free (npath); |
429 | } | | 429 | } |
430 | else | | 430 | else |
431 | #endif | | 431 | #endif |
432 | infopath = freeinfopath = | | 432 | infopath = freeinfopath = |
433 | Xasprintf ("%s/%s/%s", cvsroot, CVSROOTADM, CVSROOTADM_CONFIG); | | 433 | Xasprintf ("%s/%s/%s", cvsroot, CVSROOTADM, CVSROOTADM_CONFIG); |
434 | | | 434 | |
435 | retval = new_config (); | | 435 | retval = new_config (); |
436 | | | 436 | |
437 | fp_info = CVS_FOPEN (infopath, "r"); | | 437 | fp_info = CVS_FOPEN (infopath, "r"); |
438 | if (!fp_info) | | 438 | if (!fp_info) |
439 | { | | 439 | { |
440 | /* If no file, don't do anything special. */ | | 440 | /* If no file, don't do anything special. */ |
441 | if (!existence_error (errno)) | | 441 | if (!existence_error (errno)) |
442 | { | | 442 | { |
443 | /* Just a warning message; doesn't affect return | | 443 | /* Just a warning message; doesn't affect return |
444 | value, currently at least. */ | | 444 | value, currently at least. */ |
445 | error (0, errno, "cannot open %s", infopath); | | 445 | error (0, errno, "cannot open %s", infopath); |
446 | } | | 446 | } |
447 | if (freeinfopath) free (freeinfopath); | | 447 | if (freeinfopath) free (freeinfopath); |
448 | return retval; | | 448 | return retval; |
449 | } | | 449 | } |
450 | | | 450 | |
451 | ln = 0; /* Have not read any lines yet. */ | | 451 | ln = 0; /* Have not read any lines yet. */ |
452 | while (getline (&buf, &buf_allocated, fp_info) >= 0) | | 452 | while (getline (&buf, &buf_allocated, fp_info) >= 0) |
453 | { | | 453 | { |
454 | ln++; /* Keep track of input file line number for error messages. */ | | 454 | ln++; /* Keep track of input file line number for error messages. */ |
455 | | | 455 | |
456 | line = buf; | | 456 | line = buf; |
457 | | | 457 | |
458 | /* Skip leading white space. */ | | 458 | /* Skip leading white space. */ |
459 | while (isspace (*line)) line++; | | 459 | while (isspace (*line)) line++; |
460 | | | 460 | |
461 | /* Skip comments. */ | | 461 | /* Skip comments. */ |
462 | if (line[0] == '#') | | 462 | if (line[0] == '#') |
463 | continue; | | 463 | continue; |
464 | | | 464 | |
465 | /* Is there any kind of written standard for the syntax of this | | 465 | /* Is there any kind of written standard for the syntax of this |
466 | sort of config file? Anywhere in POSIX for example (I guess | | 466 | sort of config file? Anywhere in POSIX for example (I guess |
467 | makefiles are sort of close)? Red Hat Linux has a bunch of | | 467 | makefiles are sort of close)? Red Hat Linux has a bunch of |
468 | these too (with some GUI tools which edit them)... | | 468 | these too (with some GUI tools which edit them)... |
469 | | | 469 | |
470 | Along the same lines, we might want a table of keywords, | | 470 | Along the same lines, we might want a table of keywords, |
471 | with various types (boolean, string, &c), as a mechanism | | 471 | with various types (boolean, string, &c), as a mechanism |
472 | for making sure the syntax is consistent. Any good examples | | 472 | for making sure the syntax is consistent. Any good examples |
473 | to follow there (Apache?)? */ | | 473 | to follow there (Apache?)? */ |
474 | | | 474 | |
475 | /* Strip the trailing newline. There will be one unless we | | 475 | /* Strip the trailing newline. There will be one unless we |
476 | read a partial line without a newline, and then got end of | | 476 | read a partial line without a newline, and then got end of |
477 | file (or error?). */ | | 477 | file (or error?). */ |
478 | | | 478 | |
479 | len = strlen (line) - 1; | | 479 | len = strlen (line) - 1; |
480 | if (line[len] == '\n') | | 480 | if (line[len] == '\n') |
481 | line[len--] = '\0'; | | 481 | line[len--] = '\0'; |
482 | | | 482 | |
483 | /* Skip blank lines. */ | | 483 | /* Skip blank lines. */ |
484 | if (line[0] == '\0') | | 484 | if (line[0] == '\0') |
485 | continue; | | 485 | continue; |
486 | | | 486 | |
487 | TRACE (TRACE_DATA, "parse_info() examining line: `%s'", line); | | 487 | TRACE (TRACE_DATA, "parse_info() examining line: `%s'", line); |
488 | | | 488 | |
489 | /* Check for a root specification. */ | | 489 | /* Check for a root specification. */ |
490 | if (line[0] == '[' && line[len] == ']') | | 490 | if (line[0] == '[' && line[len] == ']') |
491 | { | | 491 | { |
492 | cvsroot_t *tmproot; | | 492 | cvsroot_t *tmproot; |
493 | | | 493 | |
494 | line++[len] = '\0'; | | 494 | line++[len] = '\0'; |
495 | tmproot = parse_cvsroot (line); | | 495 | tmproot = parse_cvsroot (line); |
496 | | | 496 | |
497 | /* Ignoring method. */ | | 497 | /* Ignoring method. */ |
498 | if (!tmproot | | 498 | if (!tmproot |
499 | #if defined CLIENT_SUPPORT || defined SERVER_SUPPORT | | 499 | #if defined CLIENT_SUPPORT || defined SERVER_SUPPORT |
500 | || (tmproot->method != local_method | | 500 | || (tmproot->method != local_method |
501 | && (!tmproot->hostname || !isThisHost (tmproot->hostname))) | | 501 | && (!tmproot->hostname || !isThisHost (tmproot->hostname))) |
502 | #endif /* CLIENT_SUPPORT || SERVER_SUPPORT */ | | 502 | #endif /* CLIENT_SUPPORT || SERVER_SUPPORT */ |
503 | || !isSamePath (tmproot->directory, cvsroot)) | | 503 | || !isSamePath (tmproot->directory, cvsroot)) |
504 | { | | 504 | { |
505 | if (processed) processing = false; | | 505 | if (processed) processing = false; |
506 | } | | 506 | } |
507 | else | | 507 | else |
508 | { | | 508 | { |
509 | TRACE (TRACE_FLOW, "Matched root section`%s'", line); | | 509 | TRACE (TRACE_FLOW, "Matched root section`%s'", line); |
510 | processing = true; | | 510 | processing = true; |
511 | processed = false; | | 511 | processed = false; |
512 | } | | 512 | } |
513 | | | 513 | |
514 | continue; | | 514 | continue; |
515 | } | | 515 | } |
516 | | | 516 | |
517 | /* There is data on this line. */ | | 517 | /* There is data on this line. */ |
518 | | | 518 | |
519 | /* Even if the data is bad or ignored, consider data processed for | | 519 | /* Even if the data is bad or ignored, consider data processed for |
520 | * this root. | | 520 | * this root. |
521 | */ | | 521 | */ |
522 | processed = true; | | 522 | processed = true; |
523 | | | 523 | |
524 | if (!processing) | | 524 | if (!processing) |
525 | /* ...but it is for a different root. */ | | 525 | /* ...but it is for a different root. */ |
526 | continue; | | 526 | continue; |
527 | | | 527 | |
528 | /* The first '=' separates keyword from value. */ | | 528 | /* The first '=' separates keyword from value. */ |
529 | p = strchr (line, '='); | | 529 | p = strchr (line, '='); |
530 | if (!p) | | 530 | if (!p) |
531 | { | | 531 | { |
532 | if (!parse_error (infopath, ln)) | | 532 | if (!parse_error (infopath, ln)) |
533 | error (0, 0, | | 533 | error (0, 0, |
534 | "%s [%d]: syntax error: missing `=' between keyword and value", | | 534 | "%s [%d]: syntax error: missing `=' between keyword and value", |
535 | infopath, ln); | | 535 | infopath, ln); |
536 | continue; | | 536 | continue; |
537 | } | | 537 | } |
538 | | | 538 | |
539 | *p++ = '\0'; | | 539 | *p++ = '\0'; |
540 | | | 540 | |
541 | if (strcmp (line, "RCSBIN") == 0) | | 541 | if (strcmp (line, "RCSBIN") == 0) |
542 | { | | 542 | { |
543 | /* This option used to specify the directory for RCS | | 543 | /* This option used to specify the directory for RCS |
544 | executables. But since we don't run them any more, | | 544 | executables. But since we don't run them any more, |
545 | this is a noop. Silently ignore it so that a | | 545 | this is a noop. Silently ignore it so that a |
546 | repository can work with either new or old CVS. */ | | 546 | repository can work with either new or old CVS. */ |
547 | ; | | 547 | ; |
548 | } | | 548 | } |
549 | else if (strcmp (line, "SystemAuth") == 0) | | 549 | else if (strcmp (line, "SystemAuth") == 0) |
550 | #ifdef AUTH_SERVER_SUPPORT | | 550 | #ifdef AUTH_SERVER_SUPPORT |
551 | readBool (infopath, "SystemAuth", p, &retval->system_auth); | | 551 | readBool (infopath, "SystemAuth", p, &retval->system_auth); |
552 | #else | | 552 | #else |
553 | { | | 553 | { |
554 | /* Still parse the syntax but ignore the option. That way the same | | 554 | /* Still parse the syntax but ignore the option. That way the same |
555 | * config file can be used for local and server. | | 555 | * config file can be used for local and server. |
556 | */ | | 556 | */ |
557 | bool dummy; | | 557 | bool dummy; |
558 | readBool (infopath, "SystemAuth", p, &dummy); | | 558 | readBool (infopath, "SystemAuth", p, &dummy); |
559 | } | | 559 | } |
560 | #endif | | 560 | #endif |
561 | else if (strcmp (line, "LocalKeyword") == 0 || | | 561 | else if (strcmp (line, "LocalKeyword") == 0 || |
562 | strcmp (line, "tag") == 0) | | 562 | strcmp (line, "tag") == 0) |
563 | RCS_setlocalid (infopath, ln, &retval->keywords, p); | | 563 | RCS_setlocalid (infopath, ln, &retval->keywords, p); |
564 | else if (strcmp (line, "KeywordExpand") == 0) | | 564 | else if (strcmp (line, "KeywordExpand") == 0) |
565 | RCS_setincexc (&retval->keywords, p); | | 565 | RCS_setincexc (&retval->keywords, p); |
566 | else if (strcmp (line, "PreservePermissions") == 0) | | 566 | else if (strcmp (line, "PreservePermissions") == 0) |
567 | { | | 567 | { |
568 | #ifdef PRESERVE_PERMISSIONS_SUPPORT | | 568 | #ifdef PRESERVE_PERMISSIONS_SUPPORT |
569 | readBool (infopath, "PreservePermissions", p, | | 569 | readBool (infopath, "PreservePermissions", p, |
570 | &retval->preserve_perms); | | 570 | &retval->preserve_perms); |
571 | #else | | 571 | #else |
572 | if (!parse_error (infopath, ln)) | | 572 | if (!parse_error (infopath, ln)) |
573 | error (0, 0, "\ | | 573 | error (0, 0, "\ |
574 | %s [%u]: warning: this CVS does not support PreservePermissions", | | 574 | %s [%u]: warning: this CVS does not support PreservePermissions", |
575 | infopath, ln); | | 575 | infopath, ln); |
576 | #endif | | 576 | #endif |
577 | } | | 577 | } |
578 | else if (strcmp (line, "TopLevelAdmin") == 0) | | 578 | else if (strcmp (line, "TopLevelAdmin") == 0) |
579 | readBool (infopath, "TopLevelAdmin", p, &retval->top_level_admin); | | 579 | readBool (infopath, "TopLevelAdmin", p, &retval->top_level_admin); |
580 | else if (strcmp (line, "LockDir") == 0) | | 580 | else if (strcmp (line, "LockDir") == 0) |
581 | { | | 581 | { |
582 | if (retval->lock_dir) | | 582 | if (retval->lock_dir) |
583 | free (retval->lock_dir); | | 583 | free (retval->lock_dir); |
584 | retval->lock_dir = expand_path (p, cvsroot, false, infopath, ln); | | 584 | retval->lock_dir = expand_path (p, cvsroot, false, infopath, ln); |
585 | /* Could try some validity checking, like whether we can | | 585 | /* Could try some validity checking, like whether we can |
586 | opendir it or something, but I don't see any particular | | 586 | opendir it or something, but I don't see any particular |
587 | reason to do that now rather than waiting until lock.c. */ | | 587 | reason to do that now rather than waiting until lock.c. */ |
588 | } | | 588 | } |
589 | else if (strcmp (line, "HistoryLogPath") == 0) | | 589 | else if (strcmp (line, "HistoryLogPath") == 0) |
590 | { | | 590 | { |
591 | if (retval->HistoryLogPath) free (retval->HistoryLogPath); | | 591 | if (retval->HistoryLogPath) free (retval->HistoryLogPath); |
592 | | | 592 | |
593 | /* Expand ~ & $VARs. */ | | 593 | /* Expand ~ & $VARs. */ |
594 | retval->HistoryLogPath = expand_path (p, cvsroot, false, | | 594 | retval->HistoryLogPath = expand_path (p, cvsroot, false, |
595 | infopath, ln); | | 595 | infopath, ln); |
596 | | | 596 | |
597 | if (retval->HistoryLogPath && !ISABSOLUTE (retval->HistoryLogPath)) | | 597 | if (retval->HistoryLogPath && !ISABSOLUTE (retval->HistoryLogPath)) |
598 | { | | 598 | { |
599 | error (0, 0, "%s [%u]: HistoryLogPath must be absolute.", | | 599 | error (0, 0, "%s [%u]: HistoryLogPath must be absolute.", |
600 | infopath, ln); | | 600 | infopath, ln); |
601 | free (retval->HistoryLogPath); | | 601 | free (retval->HistoryLogPath); |
602 | retval->HistoryLogPath = NULL; | | 602 | retval->HistoryLogPath = NULL; |
603 | } | | 603 | } |
604 | } | | 604 | } |
605 | else if (strcmp (line, "HistorySearchPath") == 0) | | 605 | else if (strcmp (line, "HistorySearchPath") == 0) |
606 | { | | 606 | { |
607 | if (retval->HistorySearchPath) free (retval->HistorySearchPath); | | 607 | if (retval->HistorySearchPath) free (retval->HistorySearchPath); |
608 | retval->HistorySearchPath = expand_path (p, cvsroot, false, | | 608 | retval->HistorySearchPath = expand_path (p, cvsroot, false, |
609 | infopath, ln); | | 609 | infopath, ln); |
610 | | | 610 | |
611 | if (retval->HistorySearchPath | | 611 | if (retval->HistorySearchPath |
612 | && !ISABSOLUTE (retval->HistorySearchPath)) | | 612 | && !ISABSOLUTE (retval->HistorySearchPath)) |
613 | { | | 613 | { |
614 | error (0, 0, "%s [%u]: HistorySearchPath must be absolute.", | | 614 | error (0, 0, "%s [%u]: HistorySearchPath must be absolute.", |
615 | infopath, ln); | | 615 | infopath, ln); |
616 | free (retval->HistorySearchPath); | | 616 | free (retval->HistorySearchPath); |
617 | retval->HistorySearchPath = NULL; | | 617 | retval->HistorySearchPath = NULL; |
618 | } | | 618 | } |
619 | } | | 619 | } |
620 | else if (strcmp (line, "LogHistory") == 0) | | 620 | else if (strcmp (line, "LogHistory") == 0) |
621 | { | | 621 | { |
622 | if (strcmp (p, "all") != 0) | | 622 | if (strcmp (p, "all") != 0) |
623 | { | | 623 | { |
624 | static bool gotone = false; | | 624 | static bool gotone = false; |
625 | if (gotone) | | 625 | if (gotone) |
626 | error (0, 0, "\ | | 626 | error (0, 0, "\ |
627 | %s [%u]: warning: duplicate LogHistory entry found.", | | 627 | %s [%u]: warning: duplicate LogHistory entry found.", |
628 | infopath, ln); | | 628 | infopath, ln); |
629 | else | | 629 | else |
630 | gotone = true; | | 630 | gotone = true; |
631 | free (retval->logHistory); | | 631 | free (retval->logHistory); |
632 | retval->logHistory = xstrdup (p); | | 632 | retval->logHistory = xstrdup (p); |
633 | } | | 633 | } |
634 | } | | 634 | } |
635 | else if (strcmp (line, "RereadLogAfterVerify") == 0) | | 635 | else if (strcmp (line, "RereadLogAfterVerify") == 0) |
636 | { | | 636 | { |
637 | if (!strcasecmp (p, "never")) | | 637 | if (!strcasecmp (p, "never")) |
638 | retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER; | | 638 | retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER; |
639 | else if (!strcasecmp (p, "always")) | | 639 | else if (!strcasecmp (p, "always")) |
640 | retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; | | 640 | retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; |
641 | else if (!strcasecmp (p, "stat")) | | 641 | else if (!strcasecmp (p, "stat")) |
642 | retval->RereadLogAfterVerify = LOGMSG_REREAD_STAT; | | 642 | retval->RereadLogAfterVerify = LOGMSG_REREAD_STAT; |
643 | else | | 643 | else |
644 | { | | 644 | { |
645 | bool tmp; | | 645 | bool tmp; |
646 | if (readBool (infopath, "RereadLogAfterVerify", p, &tmp)) | | 646 | if (readBool (infopath, "RereadLogAfterVerify", p, &tmp)) |
647 | { | | 647 | { |
648 | if (tmp) | | 648 | if (tmp) |
649 | retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; | | 649 | retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; |
650 | else | | 650 | else |
651 | retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER; | | 651 | retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER; |
652 | } | | 652 | } |
653 | } | | 653 | } |
654 | } | | 654 | } |
655 | else if (strcmp (line, "TmpDir") == 0) | | 655 | else if (strcmp (line, "TmpDir") == 0) |
656 | { | | 656 | { |
657 | if (retval->TmpDir) free (retval->TmpDir); | | 657 | if (retval->TmpDir) free (retval->TmpDir); |
658 | retval->TmpDir = expand_path (p, cvsroot, false, infopath, ln); | | 658 | retval->TmpDir = expand_path (p, cvsroot, false, infopath, ln); |
659 | /* Could try some validity checking, like whether we can | | 659 | /* Could try some validity checking, like whether we can |
660 | * opendir it or something, but I don't see any particular | | 660 | * opendir it or something, but I don't see any particular |
661 | * reason to do that now rather than when the first function | | 661 | * reason to do that now rather than when the first function |
662 | * tries to create a temp file. | | 662 | * tries to create a temp file. |
663 | */ | | 663 | */ |
664 | } | | 664 | } |
665 | else if (strcmp (line, "UserAdminGroup") == 0 | | 665 | else if (strcmp (line, "UserAdminGroup") == 0 |
666 | || strcmp (line, "AdminGroup") == 0) | | 666 | || strcmp (line, "AdminGroup") == 0) |
667 | retval->UserAdminGroup = xstrdup (p); | | 667 | retval->UserAdminGroup = xstrdup (p); |
668 | else if (strcmp (line, "UserAdminOptions") == 0 | | 668 | else if (strcmp (line, "UserAdminOptions") == 0 |
669 | || strcmp (line, "AdminOptions") == 0) | | 669 | || strcmp (line, "AdminOptions") == 0) |
670 | retval->UserAdminOptions = xstrdup (p); | | 670 | retval->UserAdminOptions = xstrdup (p); |
671 | else if (strcmp (line, "UseNewInfoFmtStrings") == 0) | | 671 | else if (strcmp (line, "UseNewInfoFmtStrings") == 0) |
672 | #ifdef SUPPORT_OLD_INFO_FMT_STRINGS | | 672 | #ifdef SUPPORT_OLD_INFO_FMT_STRINGS |
673 | readBool (infopath, "UseNewInfoFmtStrings", p, | | 673 | readBool (infopath, "UseNewInfoFmtStrings", p, |
674 | &retval->UseNewInfoFmtStrings); | | 674 | &retval->UseNewInfoFmtStrings); |
675 | #else /* !SUPPORT_OLD_INFO_FMT_STRINGS */ | | 675 | #else /* !SUPPORT_OLD_INFO_FMT_STRINGS */ |
676 | { | | 676 | { |
677 | bool dummy; | | 677 | bool dummy; |
678 | if (readBool (infopath, "UseNewInfoFmtStrings", p, &dummy) | | 678 | if (readBool (infopath, "UseNewInfoFmtStrings", p, &dummy) |
679 | && !dummy) | | 679 | && !dummy) |
680 | error (1, 0, | | 680 | error (1, 0, |
681 | "%s [%u]: Old style info format strings not supported by this executable.", | | 681 | "%s [%u]: Old style info format strings not supported by this executable.", |
682 | infopath, ln); | | 682 | infopath, ln); |
683 | } | | 683 | } |
684 | #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */ | | 684 | #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */ |
685 | else if (strcmp (line, "ImportNewFilesToVendorBranchOnly") == 0) | | 685 | else if (strcmp (line, "ImportNewFilesToVendorBranchOnly") == 0) |
686 | readBool (infopath, "ImportNewFilesToVendorBranchOnly", p, | | 686 | readBool (infopath, "ImportNewFilesToVendorBranchOnly", p, |
687 | &retval->ImportNewFilesToVendorBranchOnly); | | 687 | &retval->ImportNewFilesToVendorBranchOnly); |
688 | else if (strcmp (line, "PrimaryServer") == 0) | | 688 | else if (strcmp (line, "PrimaryServer") == 0) |
689 | retval->PrimaryServer = parse_cvsroot (p); | | 689 | retval->PrimaryServer = parse_cvsroot (p); |
690 | #ifdef PROXY_SUPPORT | | 690 | #ifdef PROXY_SUPPORT |
691 | else if (!strcmp (line, "MaxProxyBufferSize")) | | 691 | else if (!strcmp (line, "MaxProxyBufferSize")) |
692 | readSizeT (infopath, "MaxProxyBufferSize", p, | | 692 | readSizeT (infopath, "MaxProxyBufferSize", p, |
693 | &retval->MaxProxyBufferSize); | | 693 | &retval->MaxProxyBufferSize); |
694 | #endif /* PROXY_SUPPORT */ | | 694 | #endif /* PROXY_SUPPORT */ |
695 | else if (!strcmp (line, "MaxCommentLeaderLength")) | | 695 | else if (!strcmp (line, "MaxCommentLeaderLength")) |
696 | readSizeT (infopath, "MaxCommentLeaderLength", p, | | 696 | readSizeT (infopath, "MaxCommentLeaderLength", p, |
697 | &retval->MaxCommentLeaderLength); | | 697 | &retval->MaxCommentLeaderLength); |
698 | else if (!strcmp (line, "UseArchiveCommentLeader")) | | 698 | else if (!strcmp (line, "UseArchiveCommentLeader")) |
699 | readBool (infopath, "UseArchiveCommentLeader", p, | | 699 | readBool (infopath, "UseArchiveCommentLeader", p, |
700 | &retval->UseArchiveCommentLeader); | | 700 | &retval->UseArchiveCommentLeader); |
701 | #ifdef SERVER_SUPPORT | | 701 | #ifdef SERVER_SUPPORT |
702 | else if (!strcmp (line, "MinCompressionLevel")) | | 702 | else if (!strcmp (line, "MinCompressionLevel")) |
703 | readSizeT (infopath, "MinCompressionLevel", p, | | 703 | readSizeT (infopath, "MinCompressionLevel", p, |
704 | &retval->MinCompressionLevel); | | 704 | &retval->MinCompressionLevel); |
705 | else if (!strcmp (line, "MaxCompressionLevel")) | | 705 | else if (!strcmp (line, "MaxCompressionLevel")) |
706 | readSizeT (infopath, "MaxCompressionLevel", p, | | 706 | readSizeT (infopath, "MaxCompressionLevel", p, |
707 | &retval->MaxCompressionLevel); | | 707 | &retval->MaxCompressionLevel); |
708 | #endif /* SERVER_SUPPORT */ | | 708 | #endif /* SERVER_SUPPORT */ |
709 | else | | 709 | else |
710 | /* We may be dealing with a keyword which was added in a | | 710 | /* We may be dealing with a keyword which was added in a |
711 | subsequent version of CVS. In that case it is a good idea | | 711 | subsequent version of CVS. In that case it is a good idea |
712 | to complain, as (1) the keyword might enable a behavior like | | 712 | to complain, as (1) the keyword might enable a behavior like |
713 | alternate locking behavior, in which it is dangerous and hard | | 713 | alternate locking behavior, in which it is dangerous and hard |
714 | to detect if some CVS's have it one way and others have it | | 714 | to detect if some CVS's have it one way and others have it |
715 | the other way, (2) in general, having us not do what the user | | 715 | the other way, (2) in general, having us not do what the user |
716 | had in mind when they put in the keyword violates the | | 716 | had in mind when they put in the keyword violates the |
717 | principle of least surprise. Note that one corollary is | | 717 | principle of least surprise. Note that one corollary is |
718 | adding new keywords to your CVSROOT/config file is not | | 718 | adding new keywords to your CVSROOT/config file is not |
719 | particularly recommended unless you are planning on using | | 719 | particularly recommended unless you are planning on using |
720 | the new features. */ | | 720 | the new features. */ |
721 | if (!parse_error (infopath, ln)) | | 721 | if (!parse_error (infopath, ln)) |
722 | error (0, 0, "%s [%u]: unrecognized keyword `%s'", | | 722 | error (0, 0, "%s [%u]: unrecognized keyword `%s'", |
723 | infopath, ln, line); | | 723 | infopath, ln, line); |
724 | } | | 724 | } |
725 | if (ferror (fp_info)) | | 725 | if (ferror (fp_info)) |
726 | error (0, errno, "cannot read %s", infopath); | | 726 | error (0, errno, "cannot read %s", infopath); |
727 | if (fclose (fp_info) < 0) | | 727 | if (fclose (fp_info) < 0) |
728 | error (0, errno, "cannot close %s", infopath); | | 728 | error (0, errno, "cannot close %s", infopath); |
729 | if (freeinfopath) free (freeinfopath); | | 729 | if (freeinfopath) free (freeinfopath); |
730 | if (buf) free (buf); | | 730 | if (buf) free (buf); |
731 | | | 731 | |
732 | return retval; | | 732 | return retval; |
733 | } | | 733 | } |
| | | 734 | |
| | | 735 | /* cvsacl patch */ |
| | | 736 | int |
| | | 737 | parse_aclconfig (const char *cvsroot) |
| | | 738 | { |
| | | 739 | char *infopath; |
| | | 740 | FILE *fp_info; |
| | | 741 | char *line = NULL; |
| | | 742 | size_t line_allocated = 0; |
| | | 743 | size_t len; |
| | | 744 | char *p; |
| | | 745 | /* FIXME-reentrancy: If we do a multi-threaded server, this would need |
| | | 746 | to go to the per-connection data structures. */ |
| | | 747 | static int parsed = 0; |
| | | 748 | |
| | | 749 | /* Authentication code and serve_root might both want to call us. |
| | | 750 | Let this happen smoothly. */ |
| | | 751 | if (parsed) |
| | | 752 | return 0; |
| | | 753 | parsed = 1; |
| | | 754 | |
| | | 755 | infopath = xmalloc (strlen (cvsroot) |
| | | 756 | + sizeof (CVSROOTADM_ACLCONFIG) |
| | | 757 | + sizeof (CVSROOTADM) |
| | | 758 | + 10); |
| | | 759 | if (infopath == NULL) |
| | | 760 | { |
| | | 761 | error (0, 0, "out of memory; cannot allocate infopath"); |
| | | 762 | goto error_return; |
| | | 763 | } |
| | | 764 | |
| | | 765 | strcpy (infopath, cvsroot); |
| | | 766 | strcat (infopath, "/"); |
| | | 767 | strcat (infopath, CVSROOTADM); |
| | | 768 | strcat (infopath, "/"); |
| | | 769 | strcat (infopath, CVSROOTADM_ACLCONFIG); |
| | | 770 | |
| | | 771 | fp_info = CVS_FOPEN (infopath, "r"); |
| | | 772 | if (fp_info == NULL) |
| | | 773 | { |
| | | 774 | /* If no file, don't do anything special. */ |
| | | 775 | if (!existence_error (errno)) |
| | | 776 | { |
| | | 777 | /* Just a warning message; doesn't affect return |
| | | 778 | value, currently at least. */ |
| | | 779 | error (0, errno, "cannot open %s", infopath); |
| | | 780 | } |
| | | 781 | free (infopath); |
| | | 782 | return 0; |
| | | 783 | } |
| | | 784 | |
| | | 785 | while (getline (&line, &line_allocated, fp_info) >= 0) |
| | | 786 | { |
| | | 787 | /* Skip comments. */ |
| | | 788 | if (line[0] == '#') |
| | | 789 | continue; |
| | | 790 | |
| | | 791 | len = strlen (line) - 1; |
| | | 792 | if (line[len] == '\n') |
| | | 793 | line[len] = '\0'; |
| | | 794 | |
| | | 795 | /* Skip blank lines. */ |
| | | 796 | if (line[0] == '\0') |
| | | 797 | continue; |
| | | 798 | |
| | | 799 | /* The first '=' separates keyword from value. */ |
| | | 800 | p = strchr (line, '='); |
| | | 801 | if (p == NULL) |
| | | 802 | { |
| | | 803 | /* Probably should be printing line number. */ |
| | | 804 | error (0, 0, "syntax error in %s: line '%s' is missing '='", |
| | | 805 | infopath, line); |
| | | 806 | goto error_return; |
| | | 807 | } |
| | | 808 | |
| | | 809 | *p++ = '\0'; |
| | | 810 | |
| | | 811 | if (strcmp (line, "UseCVSACL") == 0) |
| | | 812 | { |
| | | 813 | if (strcmp (p, "no") == 0) |
| | | 814 | use_cvs_acl = 0; |
| | | 815 | else if (strcmp (p, "yes") == 0) |
| | | 816 | use_cvs_acl = 1; |
| | | 817 | else |
| | | 818 | { |
| | | 819 | error (0, 0, "unrecognized value '%s' for UseCVSACL", p); |
| | | 820 | goto error_return; |
| | | 821 | } |
| | | 822 | } |
| | | 823 | else if (strcmp (line, "UseSeperateACLFileForEachDir") == 0) |
| | | 824 | { |
| | | 825 | if (strcmp (p, "no") == 0) |
| | | 826 | use_separate_acl_file_for_each_dir = 0; |
| | | 827 | else if (strcmp (p, "yes") == 0) |
| | | 828 | use_separate_acl_file_for_each_dir = 1; |
| | | 829 | else |
| | | 830 | { |
| | | 831 | error (0, 0, "unrecognized value '%s' for UseSeperateACLFileForEachDir", p); |
| | | 832 | goto error_return; |
| | | 833 | } |
| | | 834 | } |
| | | 835 | else if (strcmp (line, "StopAtFirstPermissionDenied") == 0) |
| | | 836 | { |
| | | 837 | if (strcmp (p, "no") == 0) |
| | | 838 | stop_at_first_permission_denied = 0; |
| | | 839 | else if (strcmp (p, "yes") == 0) |
| | | 840 | stop_at_first_permission_denied = 1; |
| | | 841 | else |
| | | 842 | { |
| | | 843 | error (0, 0, "unrecognized value '%s' for StopAtFirstPermissionDenied", p); |
| | | 844 | goto error_return; |
| | | 845 | } |
| | | 846 | } |
| | | 847 | else if (strcmp (line, "CVSACLDefaultPermissions") == 0) |
| | | 848 | { |
| | | 849 | if (cvs_acl_default_permissions != NULL) |
| | | 850 | free (cvs_acl_default_permissions); |
| | | 851 | if (!given_perms_valid (p)) |
| | | 852 | error (1,0,"Invalid CVS ACL Default Permissions: '%s' in CVSROOT/aclconfig", p); |
| | | 853 | cvs_acl_default_permissions = xstrdup (p); |
| | | 854 | } |
| | | 855 | else if (strcmp (line, "UseCVSGroups") == 0) |
| | | 856 | { |
| | | 857 | if (strcmp (p, "no") == 0) |
| | | 858 | use_cvs_groups = 0; |
| | | 859 | else if (strcmp (p, "yes") == 0) |
| | | 860 | use_cvs_groups = 1; |
| | | 861 | else |
| | | 862 | { |
| | | 863 | error (0, 0, "unrecognized value '%s' for UseCVSGroups", p); |
| | | 864 | goto error_return; |
| | | 865 | } |
| | | 866 | } |
| | | 867 | else if (strcmp (line, "UseSystemGroups") == 0) |
| | | 868 | { |
| | | 869 | if (strcmp (p, "no") == 0) |
| | | 870 | use_system_groups = 0; |
| | | 871 | else if (strcmp (p, "yes") == 0) |
| | | 872 | use_system_groups = 1; |
| | | 873 | else |
| | | 874 | { |
| | | 875 | error (0, 0, "unrecognized value '%s' for UseSystemGroups", p); |
| | | 876 | goto error_return; |
| | | 877 | } |
| | | 878 | } |
| | | 879 | else if (strcmp (line, "CVSACLFileLocation") == 0) |
| | | 880 | { |
| | | 881 | if (cvs_acl_file_location != NULL) |
| | | 882 | free (cvs_acl_file_location); |
| | | 883 | cvs_acl_file_location = xstrdup (p); |
| | | 884 | } |
| | | 885 | else if (strcmp (line, "CVSGroupsFileLocation") == 0) |
| | | 886 | { |
| | | 887 | if (cvs_groups_file_location != NULL) |
| | | 888 | free (cvs_groups_file_location); |
| | | 889 | cvs_groups_file_location = xstrdup (p); |
| | | 890 | } |
| | | 891 | else if (strcmp (line, "CVSServerRunAsUser") == 0) |
| | | 892 | { |
| | | 893 | if (cvs_server_run_as != NULL) |
| | | 894 | free (cvs_server_run_as); |
| | | 895 | cvs_server_run_as = xstrdup (p); |
| | | 896 | } |
| | | 897 | |
| | | 898 | } |
| | | 899 | |
| | | 900 | if (ferror (fp_info)) |
| | | 901 | { |
| | | 902 | error (0, errno, "cannot read %s", infopath); |
| | | 903 | goto error_return; |
| | | 904 | } |
| | | 905 | if (fclose (fp_info) < 0) |
| | | 906 | { |
| | | 907 | error (0, errno, "cannot close %s", infopath); |
| | | 908 | goto error_return; |
| | | 909 | } |
| | | 910 | free (infopath); |
| | | 911 | if (line != NULL) |
| | | 912 | free (line); |
| | | 913 | return 0; |
| | | 914 | |
| | | 915 | error_return: |
| | | 916 | if (infopath != NULL) |
| | | 917 | free (infopath); |
| | | 918 | if (line != NULL) |
| | | 919 | free (line); |
| | | 920 | return -1; |
| | | 921 | } |