]> git.pld-linux.org Git - packages/cronolog.git/blob - cronolog-jumbo-patch.txt
- tabs in preamble
[packages/cronolog.git] / cronolog-jumbo-patch.txt
1 From: Matthew Grosso < mgrosso at looksmart dot net>
2 To: cronolog-users@icarus.demon.co.uk
3 Subject: CRONOLOG: jumbo patch, includes launching external program to handle logs
4 Date: Fri, 10 Oct 2003 13:02:14 -0400
5
6 I've put together a jumbo patch with some features that are on the todo 
7 list and most of the outstanding patches on the site. specifically:
8
9 the todo list items completed (which motivated the work for my companies use) are:
10
11 - ability to launch a helper program on every log file after it is closed 
12     if multiple cronologs writing to the same file, only one helper is  
13     launched.
14
15 - signal handling to ensure that every log file has a chance to be handled
16     by that helper and that cronolog doesnt ever hang apache restarts
17
18 - alarm() used so log files rotate on time period boundary even if there is no
19   traffic
20
21 I also implemented (with tweaks) many of the outstanding patches, some of
22 which are also on the todo list:
23
24 - uid/gid patch from Isaac Wilcox <iwilcox at eatstatic dot net>
25     minus the configure changes which broke the build for me.
26
27 - the large file patch from matt lanier <mlanier at danger dot .com>
28     with tweaks from me to work with my own changes above.
29
30 - incorporated a slightly different implementation of
31     the SIGUSR1 patch from Prakash Kailasa <PKailasa at seisint dot com>
32
33 - the s/stat/lstat/ fix from Victor Martinez <victor at kablinkteam dot com>
34
35 the changes have only been tested on my Linux 2.4 box, and should be considered
36 beta until more users have tried it. That said, we'll be starting to qa this at
37 looksmart soon, but again, only on linux.
38
39 I'm including a sample script that can be used as a helper. It will first
40 compress, then scp a log to multiple destinations, then remove it.
41
42 Still todo: I need to put my new options into the man page, not just the usage,
43 and see if the configure.in needs modification.
44
45 the patch is available below, and at this url:
46 http://www.falconweb.com/~mattg/code/cronolog-1.6.2-jumbo-20031010.diff
47
48 diff -Nur cronolog-1.6.2/src/cronolog.c cronolog-1.6.2-jumbo-20031008/src/cronolog.c
49 --- cronolog-1.6.2/src/cronolog.c       2001-05-03 12:42:48.000000000 -0400
50 +++ cronolog-1.6.2-jumbo-20031008/src/cronolog.c        2003-10-08 00:22:10.000000000 -0400
51 @@ -82,6 +82,18 @@
52   * written to "file" (e.g. /dev/console) or to stderr if "file" is "-".
53   */
54  
55 +#ifndef _WIN32
56 +#define _GNU_SOURCE 1
57 +#define OPEN_EXCLUSIVE O_WRONLY|O_CREAT|O_EXCL|O_APPEND|O_LARGEFILE
58 +#define OPEN_SHARED O_WRONLY|O_CREAT|O_APPEND|O_LARGEFILE
59 +#else
60 +#define OPEN_EXCLUSIVE O_WRONLY|O_CREAT|O_EXCL|O_APPEND
61 +#define OPEN_SHARED O_WRONLY|O_CREAT|O_APPEND
62 +#endif
63 +
64 +#include <sys/types.h>
65 +#include <sys/wait.h>
66 +#include <signal.h>
67  #include "cronoutils.h"
68  #include "getopt.h"
69  
70 @@ -91,6 +103,16 @@
71  int    new_log_file(const char *, const char *, mode_t, const char *,
72                      PERIODICITY, int, int, char *, size_t, time_t, time_t *);
73  
74 +void   cleanup(int );
75 +void   handle_file();
76 +void   fork_to_handle_file();
77 +
78 +int     openwrapper( const char *filename );
79 +
80 +#ifndef _WIN32
81 +void    setsig_handler( int signum,  void (*action)(int, siginfo_t *, void *));
82 +void    set_signal_handlers();
83 +#endif
84  
85  /* Definition of version and usage messages */
86  
87 @@ -100,6 +122,12 @@
88  #define VERSION_MSG      "cronolog version 0.1\n"
89  #endif
90  
91 +#ifndef _WIN32
92 +#define SETUGID_USAGE  "   -u USER,   --set-uid=USER  change to USER before doing anything (name or UID)\n" \
93 +                       "   -g GROUP,  --set-gid=GROUP change to GROUP before doing anything (name or GID)\n"
94 +#else
95 +#define SETUGID_USAGE  ""
96 +#endif
97  
98  #define USAGE_MSG      "usage: %s [OPTIONS] logfile-spec\n" \
99                         "\n" \
100 @@ -113,6 +141,11 @@
101                         "   -o,        --once-only     create single output log from template (not rotated)\n" \
102                         "   -x FILE,   --debug=FILE    write debug messages to FILE\n" \
103                         "                              ( or to standard error if FILE is \"-\")\n" \
104 +                       "   -r,        --helper=SCRIPT post rotation helper script to fork exec on old files\n" \
105 +                       "                              ( will be called like \"SCRIPT <oldlog>\" )\n" \
106 +                       "                              ( not tested on windows )\n" \
107 +                       "   -G,        --helper-arg=ARG argument passed to rotation helper script\n" \
108 +            SETUGID_USAGE \
109                         "   -a,        --american         American date formats\n" \
110                         "   -e,        --european         European date formats (default)\n" \
111                         "   -s,    --start-time=TIME   starting time\n" \
112 @@ -122,7 +155,7 @@
113  
114  /* Definition of the short and long program options */
115  
116 -char          *short_options = "ad:eop:s:z:H:P:S:l:hVx:";
117 +char          *short_options = "ad:eop:s:z:H:P:S:l:hVx:r:G:u:g:";
118  
119  #ifndef _WIN32
120  struct option long_options[] =
121 @@ -137,12 +170,23 @@
122      { "link",          required_argument,      NULL, 'l' },
123      { "period",                required_argument,      NULL, 'p' },
124      { "delay",         required_argument,      NULL, 'd' },
125 +    { "helper",                required_argument,      NULL, 'r' },
126 +    { "helper-arg",    required_argument,      NULL, 'G' },
127 +    { "set-uid",    required_argument,  NULL, 'u' },
128 +    { "set-gid",    required_argument,  NULL, 'g' },
129      { "once-only",     no_argument,            NULL, 'o' },
130      { "help",          no_argument,            NULL, 'h' },
131      { "version",       no_argument,            NULL, 'V' }
132  };
133  #endif
134  
135 +static    char handler[MAX_PATH];
136 +static    char handler_arg[MAX_PATH];
137 +static    char filename[MAX_PATH];
138 +static    int   use_handler =0;
139 +static    int   use_handler_arg =0;
140 +static    int   i_am_handler =0;
141 +
142  /* Main function.
143   */
144  int
145 @@ -155,11 +199,16 @@
146      int                use_american_date_formats = 0;
147      char       read_buf[BUFSIZE];
148      char       tzbuf[BUFSIZE];
149 -    char       filename[MAX_PATH];
150      char       *start_time = NULL;
151      char       *template;
152      char       *linkname = NULL;
153      char       *prevlinkname = NULL;
154 +#ifndef _WIN32
155 +    uid_t      new_uid = 0;
156 +    gid_t      new_gid = 0;
157 +    int                change_uid = 0;
158 +    int                change_gid = 0;
159 +#endif
160      mode_t     linktype = 0;
161      int        n_bytes_read;
162      int                ch;
163 @@ -167,6 +216,10 @@
164      time_t     time_offset = 0;
165      time_t     next_period = 0;
166      int        log_fd = -1;
167 +    
168 +    memset( handler, '\0', MAX_PATH );
169 +    memset( handler_arg, '\0', MAX_PATH );
170 +    memset( filename, '\0', MAX_PATH );
171  
172  #ifndef _WIN32
173      while ((ch = getopt_long(argc, argv, short_options, long_options, NULL)) != EOF)
174 @@ -234,6 +287,16 @@
175             }           
176             break;
177             
178 +#ifndef _WIN32
179 +       case 'u':
180 +           new_uid = parse_uid(optarg, argv[0]);
181 +           change_uid = 1;
182 +           break;
183 +       case 'g':
184 +           new_gid = parse_gid(optarg, argv[0]);
185 +           change_gid = 1;
186 +           break;
187 +#endif
188         case 'o':
189             periodicity = ONCE_ONLY;
190             break;
191 @@ -248,7 +311,15 @@
192                 debug_file = fopen(optarg, "a+");
193             }
194             break;
195 -           
196 +       case 'r':
197 +            strncat(handler, optarg, MAX_PATH );
198 +            use_handler=1;
199 +            break;
200 +       case 'G':
201 +            strncat(handler_arg, optarg, MAX_PATH );
202 +            use_handler_arg=1;
203 +            break;
204 +
205         case 'V':
206             fprintf(stderr, VERSION_MSG);
207             exit(0);
208 @@ -266,6 +337,17 @@
209         exit(1);
210      }
211  
212 +#ifndef _WIN32
213 +    if (change_gid && setgid(new_gid) == -1) {
214 +       fprintf(stderr, "setgid: unable to change to gid: %d\n", new_gid);
215 +               exit(1);
216 +    }
217 +    if (change_uid && setuid(new_uid) == -1) {
218 +       fprintf(stderr, "setuid: unable to change to uid: %d\n", new_uid);
219 +               exit(1);
220 +    }
221 +#endif
222 +
223      DEBUG((VERSION_MSG "\n"));
224  
225      if (start_time)
226 @@ -306,6 +388,10 @@
227      DEBUG(("Rotation period is per %d %s\n", period_multiple, periods[periodicity]));
228  
229  
230 +#ifndef _WIN32
231 +    set_signal_handlers();
232 +#endif
233 +
234      /* Loop, waiting for data on standard input */
235  
236      for (;;)
237 @@ -316,15 +402,17 @@
238         n_bytes_read = read(0, read_buf, sizeof read_buf);
239         if (n_bytes_read == 0)
240         {
241 -           exit(3);
242 +           cleanup(3);
243         }
244         if (errno == EINTR)
245         {
246 -           continue;
247 +           /* 
248 +             * fall through, it may have been alarm, in which case it will be time to rotate.
249 +             * */
250         }
251         else if (n_bytes_read < 0)
252         {
253 -           exit(4);
254 +           cleanup(4);
255         }
256  
257         time_now = time(NULL) + time_offset;
258 @@ -336,6 +424,7 @@
259         {
260             close(log_fd);
261             log_fd = -1;
262 +            fork_to_handle_file();
263         }
264         
265         /* If there is no log file open then open a new one.
266 @@ -345,6 +434,7 @@
267             log_fd = new_log_file(template, linkname, linktype, prevlinkname,
268                                   periodicity, period_multiple, period_delay,
269                                   filename, sizeof (filename), time_now, &next_period);
270 +            alarm( next_period - time_now );
271         }
272  
273         DEBUG(("%s (%d): wrote message; next period starts at %s (%d) in %d secs\n",
274 @@ -354,10 +444,10 @@
275  
276         /* Write out the log data to the current log file.
277          */
278 -       if (write(log_fd, read_buf, n_bytes_read) != n_bytes_read)
279 +       if (n_bytes_read && write(log_fd, read_buf, n_bytes_read) != n_bytes_read)
280         {
281             perror(filename);
282 -           exit(5);
283 +           cleanup(5);
284         }
285      }
286  
287 @@ -383,6 +473,7 @@
288      struct tm  *tm;
289      int        log_fd;
290  
291 +
292      start_of_period = start_of_this_period(time_now, periodicity, period_multiple);
293      tm = localtime(&start_of_period);
294      strftime(pfilename, BUFSIZE, template, tm);
295 @@ -394,13 +485,13 @@
296            timestamp(*pnext_period), *pnext_period,
297            *pnext_period - time_now));
298      
299 -    log_fd = open(pfilename, O_WRONLY|O_CREAT|O_APPEND, FILE_MODE);
300 +    log_fd = openwrapper(pfilename);
301      
302  #ifndef DONT_CREATE_SUBDIRS
303      if ((log_fd < 0) && (errno == ENOENT))
304      {
305         create_subdirs(pfilename);
306 -       log_fd = open(pfilename, O_WRONLY|O_CREAT|O_APPEND, FILE_MODE);
307 +       log_fd = openwrapper(pfilename);
308      }
309  #endif     
310  
311 @@ -416,3 +507,179 @@
312      }
313      return log_fd;
314  }
315 +
316 +/* 
317 + * fork, then exec an external handler to deal with rotated file.
318 + */
319 +void
320 +fork_to_handle_file()
321 +{
322 +    int fk ;
323 +    static int childpid=0;
324 +
325 +    if( ! use_handler || !i_am_handler || handler[0] =='\0' || filename[0] == '\0' )
326 +    {
327 +        return;
328 +    }
329 +    fk=fork();
330 +    if( fk < 0 )
331 +    {
332 +        perror("couldnt fork");
333 +        exit(2);
334 +    }else if( fk > 0 )
335 +    {
336 +        if( childpid )
337 +        {
338 +            /* 
339 +             * collect zombies. run twice, in case one or more children took longer than
340 +             * the rotation period for a while, this will eventually clean them up.
341 +             * Of course, if handler children take longer than rotation period to handle
342 +             * things, you will eventually have a big problem.
343 +             * 
344 +             * */ 
345 +            (void) waitpid( 0, NULL, WNOHANG | WUNTRACED );
346 +            (void) waitpid( 0, NULL, WNOHANG | WUNTRACED );
347 +        }
348 +        childpid=fk;
349 +        return; /* parent */
350 +    }
351 +    /* child */
352 +    /* dont muck with stdin or out of parent, but allow stderr to be commingled */
353 +    close(0);
354 +    close(1);
355 +    handle_file();
356 +}
357 +
358 +/* 
359 + * exec an external handler to deal with rotated file.
360 + */
361 +void 
362 +handle_file()
363 +{
364 +    char **exec_argv ;
365 +    if( ! use_handler || !i_am_handler || handler[0] =='\0' || filename[0] == '\0' )
366 +    {
367 +        return;
368 +    }
369 +    if ( use_handler_arg == 0 )
370 +    { 
371 +        exec_argv = malloc( sizeof( char *)*3);
372 +        exec_argv[0] = strdup( handler );
373 +        exec_argv[1] = strdup( filename );
374 +        exec_argv[2] = NULL;
375 +    }else
376 +    {
377 +        exec_argv = malloc( sizeof( char *)*4);
378 +        exec_argv[0] = strdup( handler );
379 +        exec_argv[1] = strdup( handler_arg );
380 +        exec_argv[2] = strdup( filename );
381 +        exec_argv[3] = NULL ;
382 +    }
383 +    execvp( exec_argv[0], exec_argv );
384 +    perror("cant execvp");
385 +    exit(2);
386 +}
387 +
388 +
389 +
390 +#ifndef _WIN32
391 +/* 
392 + * wrapper to be called as signal handler.
393 + */
394 +void 
395 +handle_file_on_sig( int sig, siginfo_t *si, void *v )
396 +{
397 +    handle_file();
398 +    /* not reached */
399 +    exit( 3 );
400 +};
401 +
402 +/* 
403 + * wrapper to be called for alarm signal
404 + */
405 +void 
406 +alarm_signal_handler( int sig, siginfo_t *si, void *v )
407 +{
408 +        ;
409 +        /* 
410 +         * do nothing; the key thing is that the alarm will cause the read()
411 +         * to fail with errno=EINTR. this empty handler is required, because the
412 +         * default handler will exit(1)
413 +         *
414 +         */
415 +};
416 +
417 +void
418 +set_signal_handlers()
419 +{
420 +    /* 
421 +     * all signals which usually kill a process that can be caught are
422 +     * set to handle_file when received. This will make apache shutdowns more 
423 +     * graceful even if use_handler is false.
424 +     */
425 +    setsig_handler( SIGHUP, handle_file_on_sig );
426 +    setsig_handler( SIGINT, handle_file_on_sig );
427 +    setsig_handler( SIGQUIT, handle_file_on_sig );
428 +    setsig_handler( SIGILL, handle_file_on_sig );
429 +    setsig_handler( SIGABRT, handle_file_on_sig );
430 +    setsig_handler( SIGBUS, handle_file_on_sig );
431 +    setsig_handler( SIGFPE, handle_file_on_sig );
432 +    setsig_handler( SIGPIPE, handle_file_on_sig );
433 +    setsig_handler( SIGTERM, handle_file_on_sig );
434 +    setsig_handler( SIGUSR1, handle_file_on_sig );
435 +
436 +    /* sigalrm is used to break out of read() when it is time to rotate the log. */
437 +    setsig_handler( SIGALRM, alarm_signal_handler );
438 +}
439 +
440 +void
441 +setsig_handler( int signum,  void (*action)(int, siginfo_t *, void *))
442 +{
443 +    struct sigaction siga ;
444 +    memset( &siga, '\0', sizeof( struct sigaction ));
445 +    siga.sa_sigaction= action ;
446 +    siga.sa_flags = SA_SIGINFO ;
447 +    if( -1== sigaction( signum, &siga, NULL ))
448 +    {
449 +        perror( "cant set sigaction" );
450 +    }
451 +}
452 +#endif
453 +
454 +
455 +/* 
456 + * cleanup
457 + */
458 +void
459 +cleanup( int exit_status )
460 +{
461 +    handle_file();
462 +    exit(exit_status);
463 +}
464 +
465 +/* 
466 + * only the first cronolog process to open a particular file is responsible
467 + * for starting the cleanup process later. This wrapper sets i_am_handler
468 + * according to that logic.
469 + * */
470 +int 
471 +openwrapper( const char *ofilename )
472 +{
473 +    int ret;
474 +    if( use_handler !=1 )
475 +    {
476 +        return open(ofilename, OPEN_SHARED, S_IRWXU );
477 +    }
478 +    ret = open(ofilename, OPEN_EXCLUSIVE, S_IRWXU );
479 +    if( ret < 0 )
480 +    {
481 +        ret = open(ofilename, OPEN_SHARED, S_IRWXU );
482 +        i_am_handler= 0;
483 +    }
484 +    else
485 +    {
486 +        i_am_handler=1;
487 +    }
488 +    return ret;
489 +}
490 +
491 diff -Nur cronolog-1.6.2/src/cronoutils.c cronolog-1.6.2-jumbo-20031008/src/cronoutils.c
492 --- cronolog-1.6.2/src/cronoutils.c     2001-05-03 12:43:21.000000000 -0400
493 +++ cronolog-1.6.2-jumbo-20031008/src/cronoutils.c      2003-10-08 00:22:10.000000000 -0400
494 @@ -195,11 +195,11 @@
495  {
496      struct stat                stat_buf;
497      
498 -    if (stat(prevlinkname, &stat_buf) == 0)
499 +    if (lstat(prevlinkname, &stat_buf) == 0)
500      {
501         unlink(prevlinkname);
502      }
503 -    if (stat(linkname, &stat_buf) == 0)
504 +    if (lstat(linkname, &stat_buf) == 0)
505      {
506         if (prevlinkname) {
507             rename(linkname, prevlinkname);
508 @@ -710,4 +710,50 @@
509      return retval;
510  }
511  
512 -    
513 +
514 +#ifndef _WIN32
515 +/* Turn a string specifying either a username or UID into an actual
516 + * uid_t for use in setuid(). A string is assumed to be a UID if
517 + * it contains only decimal digits. */
518 +uid_t
519 +parse_uid(char *user, char *argv0)
520 +{
521 +    char               *probe = user;
522 +    struct passwd      *ent;
523 +
524 +    while (*probe && isdigit(*probe)) {
525 +       probe++;
526 +    }
527 +    if (!(*probe)) {
528 +       return atoi(user);
529 +    }
530 +    if (!(ent = getpwnam(user))) {
531 +       fprintf(stderr, "%s: Bad username %s\n", argv0, user);
532 +       exit(1);
533 +    }
534 +    return (ent->pw_uid);
535 +}
536 +
537 +
538 +/* Turn a string specifying either a group name or GID into an actual
539 + * gid_t for use in setgid(). A string is assumed to be a GID if
540 + * it contains only decimal digits. */
541 +gid_t
542 +parse_gid(char *group, char *argv0)
543 +{
544 +    char               *probe = group;
545 +    struct group       *ent;
546 +
547 +    while (*probe && isdigit(*probe)) {
548 +       probe++;
549 +    }
550 +    if (!(*probe)) {
551 +       return atoi(group);
552 +    }
553 +    if (!(ent = getgrnam(group))) {
554 +       fprintf(stderr, "%s: Bad group name %s\n", argv0, group);
555 +       exit(1);
556 +    }
557 +    return (ent->gr_gid);
558 +}
559 +#endif /* _WIN32 */
560 diff -Nur cronolog-1.6.2/src/cronoutils.h cronolog-1.6.2-jumbo-20031008/src/cronoutils.h
561 --- cronolog-1.6.2/src/cronoutils.h     2001-05-03 12:40:12.000000000 -0400
562 +++ cronolog-1.6.2-jumbo-20031008/src/cronoutils.h      2003-10-08 00:22:10.000000000 -0400
563 @@ -84,6 +84,8 @@
564  #include <limits.h>
565  #ifndef _WIN32
566  #include <unistd.h>
567 +#include <pwd.h>
568 +#include <grp.h>
569  #else
570  #include <io.h>
571  #include <direct.h>
572 @@ -172,7 +174,8 @@
573  void           print_debug_msg(char *msg, ...);
574  time_t         parse_time(char *time_str, int);
575  char           *timestamp(time_t thetime);
576 -
577 +uid_t          parse_uid(char *user, char *argv0);
578 +gid_t          parse_gid(char *group, char *argv0);
579  
580  /* Global variables */
581  
582 diff -Nur cronolog-1.6.2/src/zip_send_rm.sh cronolog-1.6.2-jumbo-20031008/src/zip_send_rm.sh
583 --- cronolog-1.6.2/src/zip_send_rm.sh   1969-12-31 19:00:00.000000000 -0500
584 +++ cronolog-1.6.2-jumbo-20031008/src/zip_send_rm.sh    2003-10-10 12:37:22.000000000 -0400
585 @@ -0,0 +1,64 @@
586 +#!/bin/bash
587 +## ----------------------------------------------------------------------
588 +## ----------------------------------------------------------------------
589 +##
590 +## File:      zip_send_rm
591 +## Author:    mgrosso 
592 +## Created:   Fri Oct  3 18:18:38 EDT 2003 on dhcp-172-18-102-101.looksmart.com
593 +## Project:   apache, cronolog
594 +## Purpose:   make damn sure the log file arrives at its destinations
595 +## 
596 +## Copyright (c) 2003 LookSmart. All Rights Reserved.
597 +## 
598 +## $Id$
599 +## ----------------------------------------------------------------------
600 +## ----------------------------------------------------------------------
601 +
602 +
603 +function do_or_die()
604 +{
605 +    $@
606 +    if [ $? -ne 0 ] ; then 
607 +        logger -s -p local0.crit "$0 puking on [ $@ ]"
608 +        exit 1
609 +    fi
610 +}
611 +
612 +if [ $# == 2 ] ; then
613 +    SCPDESTLIST=$1
614 +    shift
615 +fi
616 +FILE=$1
617 +
618 +
619 +
620 +do_or_die gzip $FILE
621 +
622 +if [ -z $SCPDESTLIST ] ; then
623 +    exit 0
624 +fi
625 +
626 +FILE=${FILE}.gz
627 +
628 +# spawn subshells, so that each scp can suceed or fail on its own.
629 +# while this is less efficient from a bandwidth and cpu perspective, it 
630 +# is safer because it ensures that a hanging dest cant prevent the other 
631 +# dests from working.
632 +
633 +SCPDESTINATIONS=$( echo $SCPDESTLIST | sed -e 's/,/ /g' )
634 +for DEST in $SCPDESTINATIONS ; do
635 +    (
636 +        HDEST=$( echo $DEST | cut -d ':' -f 1 )
637 +        BACKUP_LINK=${HDEST}-$FILE
638 +        do_or_die ln $FILE $BACKUP_LINK
639 +        do_or_die scp $FILE $DEST
640 +        do_or_die rm $BACKUP_LINK
641 +    ) &
642 +done
643 +wait
644 +# we dont delete the file unless it arrive at all destinations
645 +# because if one destination failed, then ${DEST}-$FILE will still exist
646 +# even after we rm $FILE
647 +do_or_die rm $FILE
648 +exit 0
649 +
650
651
652
653
654
655 -- 
656 #######################################################################
657 # Matt Grosso           cell    (201)780-9592   mgrosso@looksmart.net #
658 # work ny (212)324-1904 fax ny  (212)324-1910   work sf (415)348-7756 #
659 # pgp public key is at http://www.falconweb.com/~mattg/publickey.pgp  #
660 #######################################################################
661
662
663
This page took 0.100737 seconds and 3 git commands to generate.