1 /* 2 * Copyright 1988,1990,1993,1994 by Paul Vixie 3 * All rights reserved 4 */ 5 6 /* 7 * Copyright (c) 1997 by Internet Software Consortium 8 * 9 * Permission to use, copy, modify, and distribute this software for any 10 * purpose with or without fee is hereby granted, provided that the above 11 * copyright notice and this permission notice appear in all copies. 12 * 13 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS 14 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 15 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE 16 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 17 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 18 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 19 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 20 * SOFTWARE. 21 */ 22 23 #if !defined(lint) && !defined(LINT) 24 static const char rcsid[] = 25 "$Id: entry.c,v 1.3 1998/08/14 00:32:39 vixie Exp $"; 26 #endif 27 28 /* vix 26jan87 [RCS'd; rest of log is in RCS file] 29 * vix 01jan87 [added line-level error recovery] 30 * vix 31dec86 [added /step to the from-to range, per bob@acornrc] 31 * vix 30dec86 [written] 32 */ 33 34 35 #include "cron.h" 36 #include <grp.h> 37 #ifdef LOGIN_CAP 38 #include <login_cap.h> 39 #endif 40 41 typedef enum ecode { 42 e_none, e_minute, e_hour, e_dom, e_month, e_dow, 43 e_cmd, e_timespec, e_username, e_group, e_option, 44 e_mem 45 #ifdef LOGIN_CAP 46 , e_class 47 #endif 48 } ecode_e; 49 50 static const char *ecodes[] = 51 { 52 "no error", 53 "bad minute", 54 "bad hour", 55 "bad day-of-month", 56 "bad month", 57 "bad day-of-week", 58 "bad command", 59 "bad time specifier", 60 "bad username", 61 "bad group name", 62 "bad option", 63 "out of memory", 64 #ifdef LOGIN_CAP 65 "bad class name", 66 #endif 67 }; 68 69 static char get_list(bitstr_t *, int, int, const char *[], int, FILE *), 70 get_range(bitstr_t *, int, int, const char *[], int, FILE *), 71 get_number(int *, int, const char *[], int, FILE *); 72 static int set_element(bitstr_t *, int, int, int); 73 74 void 75 free_entry(entry *e) 76 { 77 #ifdef LOGIN_CAP 78 if (e->class != NULL) 79 free(e->class); 80 #endif 81 if (e->cmd != NULL) 82 free(e->cmd); 83 if (e->envp != NULL) 84 env_free(e->envp); 85 free(e); 86 } 87 88 89 /* return NULL if eof or syntax error occurs; 90 * otherwise return a pointer to a new entry. 91 */ 92 entry * 93 load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw, 94 char **envp) 95 { 96 /* this function reads one crontab entry -- the next -- from a file. 97 * it skips any leading blank lines, ignores comments, and returns 98 * EOF if for any reason the entry can't be read and parsed. 99 * 100 * the entry is also parsed here. 101 * 102 * syntax: 103 * user crontab: 104 * minutes hours doms months dows cmd\n 105 * system crontab (/etc/crontab): 106 * minutes hours doms months dows USERNAME cmd\n 107 */ 108 109 ecode_e ecode = e_none; 110 entry *e; 111 int ch; 112 int len; 113 char cmd[MAX_COMMAND]; 114 char envstr[MAX_ENVSTR]; 115 char **prev_env; 116 117 Debug(DPARS, ("load_entry()...about to eat comments\n")) 118 119 skip_comments(file); 120 121 ch = get_char(file); 122 if (ch == EOF) 123 return NULL; 124 125 /* ch is now the first useful character of a useful line. 126 * it may be an @special or it may be the first character 127 * of a list of minutes. 128 */ 129 130 e = (entry *) calloc(sizeof(entry), sizeof(char)); 131 132 if (e == NULL) { 133 warn("load_entry: calloc failed"); 134 return NULL; 135 } 136 137 if (ch == '@') { 138 long interval; 139 char *endptr; 140 141 /* all of these should be flagged and load-limited; i.e., 142 * instead of @hourly meaning "0 * * * *" it should mean 143 * "close to the front of every hour but not 'til the 144 * system load is low". Problems are: how do you know 145 * what "low" means? (save me from /etc/cron.conf!) and: 146 * how to guarantee low variance (how low is low?), which 147 * means how to we run roughly every hour -- seems like 148 * we need to keep a history or let the first hour set 149 * the schedule, which means we aren't load-limited 150 * anymore. too much for my overloaded brain. (vix, jan90) 151 * HINT 152 */ 153 Debug(DPARS, ("load_entry()...about to test shortcuts\n")) 154 ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); 155 if (!strcmp("reboot", cmd)) { 156 Debug(DPARS, ("load_entry()...reboot shortcut\n")) 157 e->flags |= WHEN_REBOOT; 158 } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ 159 Debug(DPARS, ("load_entry()...yearly shortcut\n")) 160 bit_set(e->second, 0); 161 bit_set(e->minute, 0); 162 bit_set(e->hour, 0); 163 bit_set(e->dom, 0); 164 bit_set(e->month, 0); 165 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 166 e->flags |= DOW_STAR; 167 } else if (!strcmp("monthly", cmd)) { 168 Debug(DPARS, ("load_entry()...monthly shortcut\n")) 169 bit_set(e->second, 0); 170 bit_set(e->minute, 0); 171 bit_set(e->hour, 0); 172 bit_set(e->dom, 0); 173 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 174 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 175 e->flags |= DOW_STAR; 176 } else if (!strcmp("weekly", cmd)) { 177 Debug(DPARS, ("load_entry()...weekly shortcut\n")) 178 bit_set(e->second, 0); 179 bit_set(e->minute, 0); 180 bit_set(e->hour, 0); 181 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 182 e->flags |= DOM_STAR; 183 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 184 bit_set(e->dow, 0); 185 } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { 186 Debug(DPARS, ("load_entry()...daily shortcut\n")) 187 bit_set(e->second, 0); 188 bit_set(e->minute, 0); 189 bit_set(e->hour, 0); 190 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 191 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 192 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 193 } else if (!strcmp("hourly", cmd)) { 194 Debug(DPARS, ("load_entry()...hourly shortcut\n")) 195 bit_set(e->second, 0); 196 bit_set(e->minute, 0); 197 bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); 198 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 199 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 200 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 201 } else if (!strcmp("every_minute", cmd)) { 202 Debug(DPARS, ("load_entry()...every_minute shortcut\n")) 203 bit_set(e->second, 0); 204 bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1)); 205 bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); 206 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 207 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 208 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 209 } else if (!strcmp("every_second", cmd)) { 210 Debug(DPARS, ("load_entry()...every_second shortcut\n")) 211 e->flags |= SEC_RES; 212 bit_nset(e->second, 0, (LAST_SECOND-FIRST_SECOND+1)); 213 bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1)); 214 bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); 215 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 216 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 217 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 218 } else if (*cmd != '\0' && 219 (interval = strtol(cmd, &endptr, 10)) > 0 && 220 *endptr == '\0') { 221 Debug(DPARS, ("load_entry()... %ld seconds " 222 "since last run\n", interval)) 223 e->interval = interval; 224 e->flags = INTERVAL; 225 } else { 226 ecode = e_timespec; 227 goto eof; 228 } 229 /* Advance past whitespace between shortcut and 230 * username/command. 231 */ 232 Skip_Blanks(ch, file); 233 if (ch == EOF) { 234 ecode = e_cmd; 235 goto eof; 236 } 237 } else { 238 Debug(DPARS, ("load_entry()...about to parse numerics\n")) 239 bit_set(e->second, 0); 240 241 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, 242 PPC_NULL, ch, file); 243 if (ch == EOF) { 244 ecode = e_minute; 245 goto eof; 246 } 247 248 /* hours 249 */ 250 251 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, 252 PPC_NULL, ch, file); 253 if (ch == EOF) { 254 ecode = e_hour; 255 goto eof; 256 } 257 258 /* DOM (days of month) 259 */ 260 261 if (ch == '*') 262 e->flags |= DOM_STAR; 263 ch = get_list(e->dom, FIRST_DOM, LAST_DOM, 264 PPC_NULL, ch, file); 265 if (ch == EOF) { 266 ecode = e_dom; 267 goto eof; 268 } 269 270 /* month 271 */ 272 273 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, 274 MonthNames, ch, file); 275 if (ch == EOF) { 276 ecode = e_month; 277 goto eof; 278 } 279 280 /* DOW (days of week) 281 */ 282 283 if (ch == '*') 284 e->flags |= DOW_STAR; 285 ch = get_list(e->dow, FIRST_DOW, LAST_DOW, 286 DowNames, ch, file); 287 if (ch == EOF) { 288 ecode = e_dow; 289 goto eof; 290 } 291 } 292 293 /* make sundays equivalent */ 294 if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { 295 bit_set(e->dow, 0); 296 bit_set(e->dow, 7); 297 } 298 299 /* ch is the first character of a command, or a username */ 300 unget_char(ch, file); 301 302 if (!pw) { 303 char *username = cmd; /* temp buffer */ 304 char *s; 305 struct group *grp; 306 #ifdef LOGIN_CAP 307 login_cap_t *lc; 308 #endif 309 310 Debug(DPARS, ("load_entry()...about to parse username\n")) 311 ch = get_string(username, MAX_COMMAND, file, " \t"); 312 313 Debug(DPARS, ("load_entry()...got %s\n",username)) 314 if (ch == EOF) { 315 ecode = e_cmd; 316 goto eof; 317 } 318 319 /* need to have consumed blanks when checking options below */ 320 Skip_Blanks(ch, file) 321 unget_char(ch, file); 322 #ifdef LOGIN_CAP 323 if ((s = strrchr(username, '/')) != NULL) { 324 *s = '\0'; 325 e->class = strdup(s + 1); 326 if (e->class == NULL) 327 warn("strdup(\"%s\")", s + 1); 328 } else { 329 e->class = strdup(RESOURCE_RC); 330 if (e->class == NULL) 331 warn("strdup(\"%s\")", RESOURCE_RC); 332 } 333 if (e->class == NULL) { 334 ecode = e_mem; 335 goto eof; 336 } 337 if ((lc = login_getclass(e->class)) == NULL) { 338 ecode = e_class; 339 goto eof; 340 } 341 login_close(lc); 342 #endif 343 grp = NULL; 344 if ((s = strrchr(username, ':')) != NULL) { 345 *s = '\0'; 346 if ((grp = getgrnam(s + 1)) == NULL) { 347 ecode = e_group; 348 goto eof; 349 } 350 } 351 352 pw = getpwnam(username); 353 if (pw == NULL) { 354 ecode = e_username; 355 goto eof; 356 } 357 if (grp != NULL) 358 pw->pw_gid = grp->gr_gid; 359 Debug(DPARS, ("load_entry()...uid %d, gid %d\n",pw->pw_uid,pw->pw_gid)) 360 #ifdef LOGIN_CAP 361 Debug(DPARS, ("load_entry()...class %s\n",e->class)) 362 #endif 363 } 364 365 #ifndef PAM /* PAM takes care of account expiration by itself */ 366 if (pw->pw_expire && time(NULL) >= pw->pw_expire) { 367 ecode = e_username; 368 goto eof; 369 } 370 #endif /* !PAM */ 371 372 e->uid = pw->pw_uid; 373 e->gid = pw->pw_gid; 374 375 /* copy and fix up environment. some variables are just defaults and 376 * others are overrides; we process only the overrides here, defaults 377 * are handled in do_command after login.conf is processed. 378 */ 379 e->envp = env_copy(envp); 380 if (e->envp == NULL) { 381 warn("env_copy"); 382 ecode = e_mem; 383 goto eof; 384 } 385 if (!env_get("SHELL", e->envp)) { 386 prev_env = e->envp; 387 e->envp = env_set(e->envp, "SHELL=" _PATH_BSHELL); 388 if (e->envp == NULL) { 389 warn("env_set(%s)", "SHELL=" _PATH_BSHELL); 390 env_free(prev_env); 391 ecode = e_mem; 392 goto eof; 393 } 394 } 395 /* If LOGIN_CAP, this is deferred to do_command where the login class 396 * is processed. If !LOGIN_CAP, do it here. 397 */ 398 #ifndef LOGIN_CAP 399 if (!env_get("HOME", e->envp)) { 400 prev_env = e->envp; 401 len = snprintf(envstr, sizeof(envstr), "HOME=%s", pw->pw_dir); 402 if (len < sizeof(envstr)) 403 e->envp = env_set(e->envp, envstr); 404 if (len >= sizeof(envstr) || e->envp == NULL) { 405 warn("env_set(%s)", envstr); 406 env_free(prev_env); 407 ecode = e_mem; 408 goto eof; 409 } 410 } 411 #endif 412 prev_env = e->envp; 413 len = snprintf(envstr, sizeof(envstr), "LOGNAME=%s", pw->pw_name); 414 if (len < (int)sizeof(envstr)) 415 e->envp = env_set(e->envp, envstr); 416 if (len >= (int)sizeof(envstr) || e->envp == NULL) { 417 warn("env_set(%s)", envstr); 418 env_free(prev_env); 419 ecode = e_mem; 420 goto eof; 421 } 422 #if defined(BSD) 423 prev_env = e->envp; 424 len = snprintf(envstr, sizeof(envstr), "USER=%s", pw->pw_name); 425 if (len < (int)sizeof(envstr)) 426 e->envp = env_set(e->envp, envstr); 427 if (len >= (int)sizeof(envstr) || e->envp == NULL) { 428 warn("env_set(%s)", envstr); 429 env_free(prev_env); 430 ecode = e_mem; 431 goto eof; 432 } 433 #endif 434 435 Debug(DPARS, ("load_entry()...checking for command options\n")) 436 437 ch = get_char(file); 438 439 while (ch == '-') { 440 Debug(DPARS|DEXT, ("load_entry()...expecting option\n")) 441 switch (ch = get_char(file)) { 442 case 'n': 443 Debug(DPARS|DEXT, ("load_entry()...got MAIL_WHEN_ERR ('n') option\n")) 444 /* only allow the user to set the option once */ 445 if ((e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR) { 446 Debug(DPARS|DEXT, ("load_entry()...duplicate MAIL_WHEN_ERR ('n') option\n")) 447 ecode = e_option; 448 goto eof; 449 } 450 e->flags |= MAIL_WHEN_ERR; 451 break; 452 case 'q': 453 Debug(DPARS|DEXT, ("load_entry()...got DONT_LOG ('q') option\n")) 454 /* only allow the user to set the option once */ 455 if ((e->flags & DONT_LOG) == DONT_LOG) { 456 Debug(DPARS|DEXT, ("load_entry()...duplicate DONT_LOG ('q') option\n")) 457 ecode = e_option; 458 goto eof; 459 } 460 e->flags |= DONT_LOG; 461 break; 462 default: 463 Debug(DPARS|DEXT, ("load_entry()...invalid option '%c'\n", ch)) 464 ecode = e_option; 465 goto eof; 466 } 467 ch = get_char(file); 468 if (ch!='\t' && ch!=' ') { 469 ecode = e_option; 470 goto eof; 471 } 472 473 Skip_Blanks(ch, file) 474 if (ch == EOF || ch == '\n') { 475 ecode = e_cmd; 476 goto eof; 477 } 478 } 479 480 unget_char(ch, file); 481 482 Debug(DPARS, ("load_entry()...about to parse command\n")) 483 484 /* Everything up to the next \n or EOF is part of the command... 485 * too bad we don't know in advance how long it will be, since we 486 * need to malloc a string for it... so, we limit it to MAX_COMMAND. 487 */ 488 ch = get_string(cmd, MAX_COMMAND, file, "\n"); 489 490 /* a file without a \n before the EOF is rude, so we'll complain... 491 */ 492 if (ch == EOF) { 493 ecode = e_cmd; 494 goto eof; 495 } 496 497 /* got the command in the 'cmd' string; save it in *e. 498 */ 499 e->cmd = strdup(cmd); 500 if (e->cmd == NULL) { 501 warn("strdup(\"%s\")", cmd); 502 ecode = e_mem; 503 goto eof; 504 } 505 Debug(DPARS, ("load_entry()...returning successfully\n")) 506 507 /* success, fini, return pointer to the entry we just created... 508 */ 509 return e; 510 511 eof: 512 free_entry(e); 513 if (ecode != e_none && error_func) 514 (*error_func)(ecodes[(int)ecode]); 515 while (ch != EOF && ch != '\n') 516 ch = get_char(file); 517 return NULL; 518 } 519 520 521 /* 522 * bits one bit per flag, default=FALSE 523 * low, high bounds, impl. offset for bitstr 524 * names NULL or names for these elements 525 * ch current character being processed 526 * file file being read 527 */ 528 static char 529 get_list(bitstr_t *bits, int low, int high, const char *names[], int ch, 530 FILE *file) 531 { 532 int done; 533 534 /* we know that we point to a non-blank character here; 535 * must do a Skip_Blanks before we exit, so that the 536 * next call (or the code that picks up the cmd) can 537 * assume the same thing. 538 */ 539 540 Debug(DPARS|DEXT, ("get_list()...entered\n")) 541 542 /* list = range {"," range} 543 */ 544 545 /* clear the bit string, since the default is 'off'. 546 */ 547 bit_nclear(bits, 0, (high-low+1)); 548 549 /* process all ranges 550 */ 551 done = FALSE; 552 while (!done) { 553 ch = get_range(bits, low, high, names, ch, file); 554 if (ch == ',') 555 ch = get_char(file); 556 else 557 done = TRUE; 558 } 559 560 /* exiting. skip to some blanks, then skip over the blanks. 561 */ 562 Skip_Nonblanks(ch, file) 563 Skip_Blanks(ch, file) 564 565 Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch)) 566 567 return ch; 568 } 569 570 571 /* 572 * bits one bit per flag, default=FALSE 573 * low, high bounds, impl. offset for bitstr 574 * names NULL or names for these elements 575 * ch current character being processed 576 * file file being read 577 */ 578 static char 579 get_range(bitstr_t *bits, int low, int high, const char *names[], int ch, 580 FILE *file) 581 { 582 /* range = number | number "-" number [ "/" number ] 583 */ 584 585 int i, num1, num2, num3; 586 587 Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")) 588 589 if (ch == '*') { 590 /* '*' means "first-last" but can still be modified by /step 591 */ 592 num1 = low; 593 num2 = high; 594 ch = get_char(file); 595 if (ch == EOF) 596 return EOF; 597 } else { 598 if (EOF == (ch = get_number(&num1, low, names, ch, file))) 599 return EOF; 600 601 if (ch == '/') 602 num2 = high; 603 else if (ch != '-') { 604 /* not a range, it's a single number. 605 */ 606 if (EOF == set_element(bits, low, high, num1)) 607 return EOF; 608 return ch; 609 } else { 610 /* eat the dash 611 */ 612 ch = get_char(file); 613 if (ch == EOF) 614 return EOF; 615 616 /* get the number following the dash 617 */ 618 ch = get_number(&num2, low, names, ch, file); 619 if (ch == EOF) 620 return EOF; 621 } 622 } 623 624 /* check for step size 625 */ 626 if (ch == '/') { 627 /* eat the slash 628 */ 629 ch = get_char(file); 630 if (ch == EOF) 631 return EOF; 632 633 /* get the step size -- note: we don't pass the 634 * names here, because the number is not an 635 * element id, it's a step size. 'low' is 636 * sent as a 0 since there is no offset either. 637 */ 638 ch = get_number(&num3, 0, PPC_NULL, ch, file); 639 if (ch == EOF || num3 == 0) 640 return EOF; 641 } else { 642 /* no step. default==1. 643 */ 644 num3 = 1; 645 } 646 647 /* range. set all elements from num1 to num2, stepping 648 * by num3. (the step is a downward-compatible extension 649 * proposed conceptually by bob@acornrc, syntactically 650 * designed then implmented by paul vixie). 651 */ 652 for (i = num1; i <= num2; i += num3) 653 if (EOF == set_element(bits, low, high, i)) 654 return EOF; 655 656 return ch; 657 } 658 659 660 /* 661 * numptr where does the result go? 662 * low offset applied to enum result 663 * names symbolic names, if any, for enums 664 * ch current character 665 * file source 666 */ 667 static char 668 get_number(int *numptr, int low, const char *names[], int ch, FILE *file) 669 { 670 char temp[MAX_TEMPSTR], *pc; 671 int len, i, all_digits; 672 673 /* collect alphanumerics into our fixed-size temp array 674 */ 675 pc = temp; 676 len = 0; 677 all_digits = TRUE; 678 while (isalnum(ch)) { 679 if (++len >= MAX_TEMPSTR) 680 return EOF; 681 682 *pc++ = ch; 683 684 if (!isdigit(ch)) 685 all_digits = FALSE; 686 687 ch = get_char(file); 688 } 689 *pc = '\0'; 690 if (len == 0) 691 return (EOF); 692 693 /* try to find the name in the name list 694 */ 695 if (names) { 696 for (i = 0; names[i] != NULL; i++) { 697 Debug(DPARS|DEXT, 698 ("get_num, compare(%s,%s)\n", names[i], temp)) 699 if (!strcasecmp(names[i], temp)) { 700 *numptr = i+low; 701 return ch; 702 } 703 } 704 } 705 706 /* no name list specified, or there is one and our string isn't 707 * in it. either way: if it's all digits, use its magnitude. 708 * otherwise, it's an error. 709 */ 710 if (all_digits) { 711 *numptr = atoi(temp); 712 return ch; 713 } 714 715 return EOF; 716 } 717 718 719 static int 720 set_element(bitstr_t *bits, int low, int high, int number) 721 { 722 Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)) 723 724 if (number < low || number > high) 725 return EOF; 726 727 bit_set(bits, (number-low)); 728 return OK; 729 } 730