Changeset 1770 in MondoRescue for branches/stable/mindi-busybox/networking/udhcp/files.c
- Timestamp:
- Nov 6, 2007, 11:01:53 AM (16 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/stable/mindi-busybox/networking/udhcp/files.c
r821 r1770 1 /* vi: set sw=4 ts=4: */ 1 2 /* 2 3 * files.c -- DHCP server file manipulation * … … 4 5 */ 5 6 6 #include <sys/socket.h>7 #include <arpa/inet.h>8 #include <string.h>9 #include <stdlib.h>10 #include <time.h>11 #include <ctype.h>12 #include <netdb.h>13 14 7 #include <netinet/ether.h> 15 #include "static_leases.h" 16 8 9 #include "common.h" 17 10 #include "dhcpd.h" 18 11 #include "options.h" 19 #include "files.h" 20 #include "common.h" 12 13 14 /* on these functions, make sure your datatype matches */ 15 static int read_ip(const char *line, void *arg) 16 { 17 len_and_sockaddr *lsa; 18 19 lsa = host_and_af2sockaddr(line, 0, AF_INET); 20 if (!lsa) 21 return 0; 22 *(uint32_t*)arg = lsa->sin.sin_addr.s_addr; 23 free(lsa); 24 return 1; 25 } 26 27 static int read_mac(const char *line, void *arg) 28 { 29 uint8_t *mac_bytes = arg; 30 struct ether_addr *temp_ether_addr; 31 32 temp_ether_addr = ether_aton(line); 33 if (temp_ether_addr == NULL) 34 return 0; 35 memcpy(mac_bytes, temp_ether_addr, 6); 36 return 1; 37 } 38 39 40 static int read_str(const char *line, void *arg) 41 { 42 char **dest = arg; 43 44 free(*dest); 45 *dest = xstrdup(line); 46 return 1; 47 } 48 49 50 static int read_u32(const char *line, void *arg) 51 { 52 *(uint32_t*)arg = bb_strtou32(line, NULL, 10); 53 return errno == 0; 54 } 55 56 57 static int read_yn(const char *line, void *arg) 58 { 59 char *dest = arg; 60 61 if (!strcasecmp("yes", line)) { 62 *dest = 1; 63 return 1; 64 } 65 if (!strcasecmp("no", line)) { 66 *dest = 0; 67 return 1; 68 } 69 return 0; 70 } 71 72 73 /* find option 'code' in opt_list */ 74 struct option_set *find_option(struct option_set *opt_list, char code) 75 { 76 while (opt_list && opt_list->data[OPT_CODE] < code) 77 opt_list = opt_list->next; 78 79 if (opt_list && opt_list->data[OPT_CODE] == code) 80 return opt_list; 81 return NULL; 82 } 83 84 85 /* add an option to the opt_list */ 86 static void attach_option(struct option_set **opt_list, 87 const struct dhcp_option *option, char *buffer, int length) 88 { 89 struct option_set *existing, *new, **curr; 90 91 existing = find_option(*opt_list, option->code); 92 if (!existing) { 93 DEBUG("Attaching option %s to list", option->name); 94 95 #if ENABLE_FEATURE_RFC3397 96 if ((option->flags & TYPE_MASK) == OPTION_STR1035) 97 /* reuse buffer and length for RFC1035-formatted string */ 98 buffer = dname_enc(NULL, 0, buffer, &length); 99 #endif 100 101 /* make a new option */ 102 new = xmalloc(sizeof(*new)); 103 new->data = xmalloc(length + 2); 104 new->data[OPT_CODE] = option->code; 105 new->data[OPT_LEN] = length; 106 memcpy(new->data + 2, buffer, length); 107 108 curr = opt_list; 109 while (*curr && (*curr)->data[OPT_CODE] < option->code) 110 curr = &(*curr)->next; 111 112 new->next = *curr; 113 *curr = new; 114 #if ENABLE_FEATURE_RFC3397 115 if ((option->flags & TYPE_MASK) == OPTION_STR1035 && buffer != NULL) 116 free(buffer); 117 #endif 118 return; 119 } 120 121 /* add it to an existing option */ 122 DEBUG("Attaching option %s to existing member of list", option->name); 123 if (option->flags & OPTION_LIST) { 124 #if ENABLE_FEATURE_RFC3397 125 if ((option->flags & TYPE_MASK) == OPTION_STR1035) 126 /* reuse buffer and length for RFC1035-formatted string */ 127 buffer = dname_enc(existing->data + 2, 128 existing->data[OPT_LEN], buffer, &length); 129 #endif 130 if (existing->data[OPT_LEN] + length <= 255) { 131 existing->data = xrealloc(existing->data, 132 existing->data[OPT_LEN] + length + 3); 133 if ((option->flags & TYPE_MASK) == OPTION_STRING) { 134 /* ' ' can bring us to 256 - bad */ 135 if (existing->data[OPT_LEN] + length >= 255) 136 return; 137 /* add space separator between STRING options in a list */ 138 existing->data[existing->data[OPT_LEN] + 2] = ' '; 139 existing->data[OPT_LEN]++; 140 } 141 memcpy(existing->data + existing->data[OPT_LEN] + 2, buffer, length); 142 existing->data[OPT_LEN] += length; 143 } /* else, ignore the data, we could put this in a second option in the future */ 144 #if ENABLE_FEATURE_RFC3397 145 if ((option->flags & TYPE_MASK) == OPTION_STR1035 && buffer != NULL) 146 free(buffer); 147 #endif 148 } /* else, ignore the new data */ 149 } 150 151 152 /* read a dhcp option and add it to opt_list */ 153 static int read_opt(const char *const_line, void *arg) 154 { 155 struct option_set **opt_list = arg; 156 char *opt, *val, *endptr; 157 const struct dhcp_option *option; 158 int retval = 0, length; 159 char buffer[8]; 160 char *line; 161 uint16_t *result_u16 = (uint16_t *) buffer; 162 uint32_t *result_u32 = (uint32_t *) buffer; 163 164 /* Cheat, the only const line we'll actually get is "" */ 165 line = (char *) const_line; 166 opt = strtok(line, " \t="); 167 if (!opt) return 0; 168 169 option = dhcp_options; 170 while (1) { 171 if (!option->code) 172 return 0; 173 if (!strcasecmp(option->name, opt)) 174 break; 175 option++; 176 } 177 178 do { 179 val = strtok(NULL, ", \t"); 180 if (!val) break; 181 length = option_lengths[option->flags & TYPE_MASK]; 182 retval = 0; 183 opt = buffer; /* new meaning for variable opt */ 184 switch (option->flags & TYPE_MASK) { 185 case OPTION_IP: 186 retval = read_ip(val, buffer); 187 break; 188 case OPTION_IP_PAIR: 189 retval = read_ip(val, buffer); 190 val = strtok(NULL, ", \t/-"); 191 if (!val) 192 retval = 0; 193 if (retval) 194 retval = read_ip(val, buffer + 4); 195 break; 196 case OPTION_STRING: 197 #if ENABLE_FEATURE_RFC3397 198 case OPTION_STR1035: 199 #endif 200 length = strlen(val); 201 if (length > 0) { 202 if (length > 254) length = 254; 203 opt = val; 204 retval = 1; 205 } 206 break; 207 case OPTION_BOOLEAN: 208 retval = read_yn(val, buffer); 209 break; 210 case OPTION_U8: 211 buffer[0] = strtoul(val, &endptr, 0); 212 retval = (endptr[0] == '\0'); 213 break; 214 /* htonX are macros in older libc's, using temp var 215 * in code below for safety */ 216 /* TODO: use bb_strtoX? */ 217 case OPTION_U16: { 218 unsigned long tmp = strtoul(val, &endptr, 0); 219 *result_u16 = htons(tmp); 220 retval = (endptr[0] == '\0' /*&& tmp < 0x10000*/); 221 break; 222 } 223 case OPTION_S16: { 224 long tmp = strtol(val, &endptr, 0); 225 *result_u16 = htons(tmp); 226 retval = (endptr[0] == '\0'); 227 break; 228 } 229 case OPTION_U32: { 230 unsigned long tmp = strtoul(val, &endptr, 0); 231 *result_u32 = htonl(tmp); 232 retval = (endptr[0] == '\0'); 233 break; 234 } 235 case OPTION_S32: { 236 long tmp = strtol(val, &endptr, 0); 237 *result_u32 = htonl(tmp); 238 retval = (endptr[0] == '\0'); 239 break; 240 } 241 default: 242 break; 243 } 244 if (retval) 245 attach_option(opt_list, option, opt, length); 246 } while (retval && option->flags & OPTION_LIST); 247 return retval; 248 } 249 250 static int read_staticlease(const char *const_line, void *arg) 251 { 252 char *line; 253 char *mac_string; 254 char *ip_string; 255 uint8_t *mac_bytes; 256 uint32_t *ip; 257 258 /* Allocate memory for addresses */ 259 mac_bytes = xmalloc(sizeof(unsigned char) * 8); 260 ip = xmalloc(sizeof(uint32_t)); 261 262 /* Read mac */ 263 line = (char *) const_line; 264 mac_string = strtok(line, " \t"); 265 read_mac(mac_string, mac_bytes); 266 267 /* Read ip */ 268 ip_string = strtok(NULL, " \t"); 269 read_ip(ip_string, ip); 270 271 addStaticLease(arg, mac_bytes, ip); 272 273 if (ENABLE_FEATURE_UDHCP_DEBUG) printStaticLeases(arg); 274 275 return 1; 276 } 277 278 279 struct config_keyword { 280 const char *keyword; 281 int (*handler)(const char *line, void *var); 282 void *var; 283 const char *def; 284 }; 285 286 static const struct config_keyword keywords[] = { 287 /* keyword handler variable address default */ 288 {"start", read_ip, &(server_config.start_ip), "192.168.0.20"}, 289 {"end", read_ip, &(server_config.end_ip), "192.168.0.254"}, 290 {"interface", read_str, &(server_config.interface), "eth0"}, 291 {"option", read_opt, &(server_config.options), ""}, 292 {"opt", read_opt, &(server_config.options), ""}, 293 /* Avoid "max_leases value not sane" warning by setting default 294 * to default_end_ip - default_start_ip + 1: */ 295 {"max_leases", read_u32, &(server_config.max_leases), "235"}, 296 {"remaining", read_yn, &(server_config.remaining), "yes"}, 297 {"auto_time", read_u32, &(server_config.auto_time), "7200"}, 298 {"decline_time", read_u32, &(server_config.decline_time), "3600"}, 299 {"conflict_time",read_u32, &(server_config.conflict_time),"3600"}, 300 {"offer_time", read_u32, &(server_config.offer_time), "60"}, 301 {"min_lease", read_u32, &(server_config.min_lease), "60"}, 302 {"lease_file", read_str, &(server_config.lease_file), LEASES_FILE}, 303 {"pidfile", read_str, &(server_config.pidfile), "/var/run/udhcpd.pid"}, 304 {"notify_file", read_str, &(server_config.notify_file), ""}, 305 {"siaddr", read_ip, &(server_config.siaddr), "0.0.0.0"}, 306 {"sname", read_str, &(server_config.sname), ""}, 307 {"boot_file", read_str, &(server_config.boot_file), ""}, 308 {"static_lease", read_staticlease, &(server_config.static_leases), ""}, 309 /* ADDME: static lease */ 310 }; 311 21 312 22 313 /* … … 27 318 #define READ_CONFIG_BUF_SIZE 80 28 319 29 /* on these functions, make sure you datatype matches */30 static int read_ip(const char *line, void *arg)31 {32 struct in_addr *addr = arg;33 struct hostent *host;34 int retval = 1;35 36 if (!inet_aton(line, addr)) {37 if ((host = gethostbyname(line)))38 addr->s_addr = *((unsigned long *) host->h_addr_list[0]);39 else retval = 0;40 }41 return retval;42 }43 44 static int read_mac(const char *line, void *arg)45 {46 uint8_t *mac_bytes = arg;47 struct ether_addr *temp_ether_addr;48 int retval = 1;49 50 temp_ether_addr = ether_aton(line);51 52 if(temp_ether_addr == NULL)53 retval = 0;54 else55 memcpy(mac_bytes, temp_ether_addr, 6);56 57 return retval;58 }59 60 61 static int read_str(const char *line, void *arg)62 {63 char **dest = arg;64 65 free(*dest);66 *dest = strdup(line);67 68 return 1;69 }70 71 72 static int read_u32(const char *line, void *arg)73 {74 uint32_t *dest = arg;75 char *endptr;76 *dest = strtoul(line, &endptr, 0);77 return endptr[0] == '\0';78 }79 80 81 static int read_yn(const char *line, void *arg)82 {83 char *dest = arg;84 int retval = 1;85 86 if (!strcasecmp("yes", line))87 *dest = 1;88 else if (!strcasecmp("no", line))89 *dest = 0;90 else retval = 0;91 92 return retval;93 }94 95 96 /* find option 'code' in opt_list */97 struct option_set *find_option(struct option_set *opt_list, char code)98 {99 while (opt_list && opt_list->data[OPT_CODE] < code)100 opt_list = opt_list->next;101 102 if (opt_list && opt_list->data[OPT_CODE] == code) return opt_list;103 else return NULL;104 }105 106 107 /* add an option to the opt_list */108 static void attach_option(struct option_set **opt_list, struct dhcp_option *option, char *buffer, int length)109 {110 struct option_set *existing, *new, **curr;111 112 /* add it to an existing option */113 if ((existing = find_option(*opt_list, option->code))) {114 DEBUG(LOG_INFO, "Attaching option %s to existing member of list", option->name);115 if (option->flags & OPTION_LIST) {116 if (existing->data[OPT_LEN] + length <= 255) {117 existing->data = realloc(existing->data,118 existing->data[OPT_LEN] + length + 2);119 memcpy(existing->data + existing->data[OPT_LEN] + 2, buffer, length);120 existing->data[OPT_LEN] += length;121 } /* else, ignore the data, we could put this in a second option in the future */122 } /* else, ignore the new data */123 } else {124 DEBUG(LOG_INFO, "Attaching option %s to list", option->name);125 126 /* make a new option */127 new = xmalloc(sizeof(struct option_set));128 new->data = xmalloc(length + 2);129 new->data[OPT_CODE] = option->code;130 new->data[OPT_LEN] = length;131 memcpy(new->data + 2, buffer, length);132 133 curr = opt_list;134 while (*curr && (*curr)->data[OPT_CODE] < option->code)135 curr = &(*curr)->next;136 137 new->next = *curr;138 *curr = new;139 }140 }141 142 143 /* read a dhcp option and add it to opt_list */144 static int read_opt(const char *const_line, void *arg)145 {146 struct option_set **opt_list = arg;147 char *opt, *val, *endptr;148 struct dhcp_option *option;149 int retval = 0, length;150 char buffer[8];151 char *line;152 uint16_t *result_u16 = (uint16_t *) buffer;153 uint32_t *result_u32 = (uint32_t *) buffer;154 155 /* Cheat, the only const line we'll actually get is "" */156 line = (char *) const_line;157 if (!(opt = strtok(line, " \t="))) return 0;158 159 for (option = dhcp_options; option->code; option++)160 if (!strcasecmp(option->name, opt))161 break;162 163 if (!option->code) return 0;164 165 do {166 if (!(val = strtok(NULL, ", \t"))) break;167 length = option_lengths[option->flags & TYPE_MASK];168 retval = 0;169 opt = buffer; /* new meaning for variable opt */170 switch (option->flags & TYPE_MASK) {171 case OPTION_IP:172 retval = read_ip(val, buffer);173 break;174 case OPTION_IP_PAIR:175 retval = read_ip(val, buffer);176 if (!(val = strtok(NULL, ", \t/-"))) retval = 0;177 if (retval) retval = read_ip(val, buffer + 4);178 break;179 case OPTION_STRING:180 length = strlen(val);181 if (length > 0) {182 if (length > 254) length = 254;183 opt = val;184 retval = 1;185 }186 break;187 case OPTION_BOOLEAN:188 retval = read_yn(val, buffer);189 break;190 case OPTION_U8:191 buffer[0] = strtoul(val, &endptr, 0);192 retval = (endptr[0] == '\0');193 break;194 case OPTION_U16:195 *result_u16 = htons(strtoul(val, &endptr, 0));196 retval = (endptr[0] == '\0');197 break;198 case OPTION_S16:199 *result_u16 = htons(strtol(val, &endptr, 0));200 retval = (endptr[0] == '\0');201 break;202 case OPTION_U32:203 *result_u32 = htonl(strtoul(val, &endptr, 0));204 retval = (endptr[0] == '\0');205 break;206 case OPTION_S32:207 *result_u32 = htonl(strtol(val, &endptr, 0));208 retval = (endptr[0] == '\0');209 break;210 default:211 break;212 }213 if (retval)214 attach_option(opt_list, option, opt, length);215 } while (retval && option->flags & OPTION_LIST);216 return retval;217 }218 219 static int read_staticlease(const char *const_line, void *arg)220 {221 222 char *line;223 char *mac_string;224 char *ip_string;225 uint8_t *mac_bytes;226 uint32_t *ip;227 228 229 /* Allocate memory for addresses */230 mac_bytes = xmalloc(sizeof(unsigned char) * 8);231 ip = xmalloc(sizeof(uint32_t));232 233 /* Read mac */234 line = (char *) const_line;235 mac_string = strtok(line, " \t");236 read_mac(mac_string, mac_bytes);237 238 /* Read ip */239 ip_string = strtok(NULL, " \t");240 read_ip(ip_string, ip);241 242 addStaticLease(arg, mac_bytes, ip);243 244 if (ENABLE_FEATURE_UDHCP_DEBUG) printStaticLeases(arg);245 246 return 1;247 248 }249 250 251 static const struct config_keyword keywords[] = {252 /* keyword handler variable address default */253 {"start", read_ip, &(server_config.start), "192.168.0.20"},254 {"end", read_ip, &(server_config.end), "192.168.0.254"},255 {"interface", read_str, &(server_config.interface), "eth0"},256 {"option", read_opt, &(server_config.options), ""},257 {"opt", read_opt, &(server_config.options), ""},258 {"max_leases", read_u32, &(server_config.max_leases), "254"},259 {"remaining", read_yn, &(server_config.remaining), "yes"},260 {"auto_time", read_u32, &(server_config.auto_time), "7200"},261 {"decline_time",read_u32, &(server_config.decline_time),"3600"},262 {"conflict_time",read_u32,&(server_config.conflict_time),"3600"},263 {"offer_time", read_u32, &(server_config.offer_time), "60"},264 {"min_lease", read_u32, &(server_config.min_lease), "60"},265 {"lease_file", read_str, &(server_config.lease_file), LEASES_FILE},266 {"pidfile", read_str, &(server_config.pidfile), "/var/run/udhcpd.pid"},267 {"notify_file", read_str, &(server_config.notify_file), ""},268 {"siaddr", read_ip, &(server_config.siaddr), "0.0.0.0"},269 {"sname", read_str, &(server_config.sname), ""},270 {"boot_file", read_str, &(server_config.boot_file), ""},271 {"static_lease",read_staticlease, &(server_config.static_leases), ""},272 /*ADDME: static lease */273 {"", NULL, NULL, ""}274 };275 276 277 320 int read_config(const char *file) 278 321 { … … 281 324 int i, lm = 0; 282 325 283 for (i = 0; keywords[i].keyword[0]; i++)326 for (i = 0; i < ARRAY_SIZE(keywords); i++) 284 327 if (keywords[i].def[0]) 285 328 keywords[i].handler(keywords[i].def, keywords[i].var); 286 329 287 i f (!(in = fopen(file, "r"))) {288 LOG(LOG_ERR, "unable to open config file: %s", file);330 in = fopen_or_warn(file, "r"); 331 if (!in) { 289 332 return 0; 290 333 } … … 292 335 while (fgets(buffer, READ_CONFIG_BUF_SIZE, in)) { 293 336 char debug_orig[READ_CONFIG_BUF_SIZE]; 337 char *p; 294 338 295 339 lm++; 296 if (strchr(buffer, '\n')) *(strchr(buffer, '\n')) = '\0'; 340 p = strchr(buffer, '\n'); 341 if (p) *p = '\0'; 297 342 if (ENABLE_FEATURE_UDHCP_DEBUG) strcpy(debug_orig, buffer); 298 if (strchr(buffer, '#')) *(strchr(buffer, '#')) = '\0'; 343 p = strchr(buffer, '#'); 344 if (p) *p = '\0'; 299 345 300 346 if (!(token = strtok(buffer, " \t"))) continue; … … 302 348 303 349 /* eat leading whitespace */ 304 line = line + strspn(line, " \t=");350 line = skip_whitespace(line); 305 351 /* eat trailing whitespace */ 306 for (i = strlen(line); i > 0 && isspace(line[i - 1]); i--); 307 line[i] = '\0'; 308 309 for (i = 0; keywords[i].keyword[0]; i++) 352 i = strlen(line) - 1; 353 while (i >= 0 && isspace(line[i])) 354 line[i--] = '\0'; 355 356 for (i = 0; i < ARRAY_SIZE(keywords); i++) 310 357 if (!strcasecmp(token, keywords[i].keyword)) 311 358 if (!keywords[i].handler(line, keywords[i].var)) { 312 LOG(LOG_ERR, "Failure parsing line %d of %s", lm, file); 313 DEBUG(LOG_ERR, "unable to parse '%s'", debug_orig); 359 bb_error_msg("cannot parse line %d of %s", lm, file); 360 if (ENABLE_FEATURE_UDHCP_DEBUG) 361 bb_error_msg("cannot parse '%s'", debug_orig); 314 362 /* reset back to the default value */ 315 363 keywords[i].handler(keywords[i].def, keywords[i].var); … … 317 365 } 318 366 fclose(in); 367 368 server_config.start_ip = ntohl(server_config.start_ip); 369 server_config.end_ip = ntohl(server_config.end_ip); 370 319 371 return 1; 320 372 } … … 323 375 void write_leases(void) 324 376 { 325 FILE *fp; 326 unsigned int i; 327 char buf[255]; 377 int fp; 378 unsigned i; 328 379 time_t curr = time(0); 329 380 unsigned long tmp_time; 330 381 331 if (!(fp = fopen(server_config.lease_file, "w"))) {332 LOG(LOG_ERR, "Unable to open %s for writing", server_config.lease_file);382 fp = open3_or_warn(server_config.lease_file, O_WRONLY|O_CREAT|O_TRUNC, 0666); 383 if (fp < 0) { 333 384 return; 334 385 } … … 346 397 } /* else stick with the time we got */ 347 398 leases[i].expires = htonl(leases[i].expires); 348 fwrite(&leases[i], sizeof(struct dhcpOfferedAddr), 1, fp); 349 350 /* Then restore it when done. */ 399 // FIXME: error check?? 400 full_write(fp, &leases[i], sizeof(leases[i])); 401 402 /* then restore it when done */ 351 403 leases[i].expires = tmp_time; 352 404 } 353 405 } 354 fclose(fp);406 close(fp); 355 407 356 408 if (server_config.notify_file) { 357 sprintf(buf, "%s %s", server_config.notify_file, server_config.lease_file); 358 system(buf); 409 char *cmd = xasprintf("%s %s", server_config.notify_file, server_config.lease_file); 410 system(cmd); 411 free(cmd); 359 412 } 360 413 } … … 363 416 void read_leases(const char *file) 364 417 { 365 FILE *fp;418 int fp; 366 419 unsigned int i = 0; 367 420 struct dhcpOfferedAddr lease; 368 421 369 if (!(fp = fopen(file, "r"))) {370 LOG(LOG_ERR, "Unable to open %s for reading", file);422 fp = open_or_warn(file, O_RDONLY); 423 if (fp < 0) { 371 424 return; 372 425 } 373 426 374 while (i < server_config.max_leases && (fread(&lease, sizeof lease, 1, fp) == 1)) { 427 while (i < server_config.max_leases 428 && full_read(fp, &lease, sizeof(lease)) == sizeof(lease) 429 ) { 375 430 /* ADDME: is it a static lease */ 376 if (lease.yiaddr >= server_config.start && lease.yiaddr <= server_config.end) { 431 uint32_t y = ntohl(lease.yiaddr); 432 if (y >= server_config.start_ip && y <= server_config.end_ip) { 377 433 lease.expires = ntohl(lease.expires); 378 if (!server_config.remaining) lease.expires -= time(0); 434 if (!server_config.remaining) 435 lease.expires -= time(0); 379 436 if (!(add_lease(lease.chaddr, lease.yiaddr, lease.expires))) { 380 LOG(LOG_WARNING, "Too many leases while loading %s\n", file);437 bb_error_msg("too many leases while loading %s", file); 381 438 break; 382 439 } … … 384 441 } 385 442 } 386 DEBUG( LOG_INFO,"Read %d leases", i);387 fclose(fp);388 } 443 DEBUG("Read %d leases", i); 444 close(fp); 445 }
Note:
See TracChangeset
for help on using the changeset viewer.