]> git.saurik.com Git - apple/syslog.git/blobdiff - aslmanager.tproj/aslmanager.c
syslog-97.1.tar.gz
[apple/syslog.git] / aslmanager.tproj / aslmanager.c
index 38420b558a439ef4aa639f939f3a671b4990c729..747a1b9578c3464ebd44424e1b119a37f503b01c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007-2008 Apple Inc. All rights reserved.
+ * Copyright (c) 2007-2009 Apple Inc. All rights reserved.
  *
  * @APPLE_LICENSE_HEADER_START@
  * 
 #include <asl_store.h>
 
 #define SECONDS_PER_DAY 86400
-#define DEFAULT_MAX_SIZE 51200000
-#define DEFAULT_TTL 2
+#define DEFAULT_MAX_SIZE 150000000
+#define DEFAULT_TTL 7
+
+#define IndexNull (uint32_t)-1
+#define _PATH_ASL_CONF "/etc/asl.conf"
+
+/* global */
+static char *archive = NULL;
+static char *store_dir = PATH_ASL_STORE;
+static time_t ttl;
+static size_t max_size;
+static mode_t archive_mode = 0400;
+static int debug;
 
 typedef struct name_list_s
 {
@@ -48,25 +59,6 @@ typedef struct name_list_s
        struct name_list_s *next;
 } name_list_t;
 
-void
-mgr_exit(const char *store, int status)
-{
-       char *s;
-
-       if (store == NULL) exit(status);
-
-       s = NULL;
-       asprintf(&s, "%s/%s", store, FILE_ASL_STORE_SWEEP_SEMAPHORE);
-       if (s != NULL)
-       {
-               unlink(s);
-               free(s);
-       }
-       else exit(1);
-
-       exit(status);
-}
-
 name_list_t *
 add_to_list(name_list_t *l, const char *name, size_t size)
 {
@@ -119,117 +111,302 @@ free_list(name_list_t *l)
 }
 
 uint32_t
-do_match(const char *infile, const char *outfile, int do_ttl, time_t expire_time)
+do_copy(const char *infile, const char *outfile, mode_t mode)
 {
-       asl_search_result_t q, *query, *res;
-       asl_msg_t *m, *qm[1];
-       asl_file_t *in, *out;
+       asl_search_result_t *res;
+       asl_file_t *f;
        uint32_t status, i;
-       char str[64];
        uint64_t mid;
 
        if (infile == NULL) return ASL_STATUS_INVALID_ARG;
        if (outfile == NULL) return ASL_STATUS_INVALID_ARG;
 
-       in = NULL;
-       status = asl_file_open_read(infile, &in);
+       f = NULL;
+       status = asl_file_open_read(infile, &f);
        if (status != ASL_STATUS_OK) return status;
 
-       query = NULL;
-       q.count = 1;
-       q.curr = 0;
-       q.msg = qm;
-       qm[0] = NULL;
-       m = NULL;
+       res = NULL;
+       mid = 0;
+
+       status = asl_file_match(f, NULL, &res, &mid, 0, 0, 1);
+       asl_file_close(f);
 
-       if (do_ttl == 1)
+       if (status != ASL_STATUS_OK) return status;
+       if (res->count == 0)
        {
-               query = &q;
-               m = asl_new(ASL_TYPE_QUERY);
-               if (m == NULL)
+               aslresponse_free(res);
+               return ASL_STATUS_OK;
+       }
+
+       f = NULL;
+       status = asl_file_open_write(outfile, mode, -1, -1, &f);
+       if (status != ASL_STATUS_OK) return status;
+       if (f == ASL_STATUS_OK) return ASL_STATUS_FAILED;
+
+       f->flags = ASL_FILE_FLAG_UNLIMITED_CACHE | ASL_FILE_FLAG_PRESERVE_MSG_ID;
+
+       for (i = 0; i < res->count; i++)
+       {
+               mid = 0;
+               status = asl_file_save(f, res->msg[i], &mid);
+               if (status != ASL_STATUS_OK) break;
+       }
+
+       asl_file_close(f);
+       return status;
+}
+
+static char **
+_insertString(char *s, char **l, uint32_t x)
+{
+       int i, len;
+
+       if (s == NULL) return l;
+       if (l == NULL) 
+       {
+               l = (char **)malloc(2 * sizeof(char *));
+               if (l == NULL) return NULL;
+
+               l[0] = strdup(s);
+               if (l[0] == NULL)
                {
-                       asl_file_close(in);
-                       return ASL_STATUS_NO_MEMORY;
+                       free(l);
+                       return NULL;
                }
 
-               qm[0] = m;
+               l[1] = NULL;
+               return l;
+       }
+
+       for (i = 0; l[i] != NULL; i++);
+       len = i + 1; /* count the NULL on the end of the list too! */
 
-               if (expire_time != 0)
+       l = (char **)reallocf(l, (len + 1) * sizeof(char *));
+       if (l == NULL) return NULL;
+
+       if ((x >= (len - 1)) || (x == IndexNull))
+       {
+               l[len - 1] = strdup(s);
+               if (l[len - 1] == NULL)
                {
-                       snprintf(str, sizeof(str), "%llu", (long long unsigned int)expire_time);
-                       if (asl_set_query(m, ASL_KEY_EXPIRE_TIME, str, ASL_QUERY_OP_NUMERIC | ASL_QUERY_OP_GREATER_EQUAL) != 0)
-                       {
-                               asl_file_close(in);
-                               asl_free(m);
-                               return ASL_STATUS_NO_MEMORY;
-                       }
+                       free(l);
+                       return NULL;
                }
-               else
+
+               l[len] = NULL;
+               return l;
+       }
+
+       for (i = len; i > x; i--) l[i] = l[i - 1];
+       l[x] = strdup(s);
+       if (l[x] == NULL) return NULL;
+
+       return l;
+}
+
+char **
+explode(const char *s, const char *delim)
+{
+       char **l = NULL;
+       const char *p;
+       char *t, quote;
+       int i, n;
+
+       if (s == NULL) return NULL;
+
+       quote = '\0';
+
+       p = s;
+       while (p[0] != '\0')
+       {
+               /* scan forward */
+               for (i = 0; p[i] != '\0'; i++)
                {
-                       if (asl_set_query(m, ASL_KEY_EXPIRE_TIME, NULL, ASL_QUERY_OP_TRUE) != 0)
+                       if (quote == '\0')
+                       {
+                               /* not inside a quoted string: check for delimiters and quotes */
+                               if (strchr(delim, p[i]) != NULL) break;
+                               else if (p[i] == '\'') quote = p[i];
+                               else if (p[i] == '"') quote = p[i];
+                       }
+                       else
                        {
-                               asl_file_close(in);
-                               asl_free(m);
-                               return ASL_STATUS_NO_MEMORY;
+                               /* inside a quoted string - look for matching quote */
+                               if (p[i] == quote) quote = '\0';
                        }
                }
+
+               n = i;
+               t = malloc(n + 1);
+               if (t == NULL) return NULL;
+
+               for (i = 0; i < n; i++) t[i] = p[i];
+               t[n] = '\0';
+               l = _insertString(t, l, IndexNull);
+               free(t);
+               t = NULL;
+               if (p[i] == '\0') return l;
+               if (p[i + 1] == '\0') l = _insertString("", l, IndexNull);
+               p = p + i + 1;
        }
 
-       res = NULL;
-       mid = 0;
-       status = asl_file_match(in, query, &res, &mid, 0, 0, 1);
-       if (m != NULL) asl_free(m);
-       asl_file_close(in);
+       return l;
+}
 
-       if (status != ASL_STATUS_OK) return status;
+void
+freeList(char **l)
+{
+       int i;
 
-       /*
-        * N.B. "ASL_STATUS_NOT_FOUND" is never returned by asl_file_match.
-        * We use it here to signal the caller that no records were found by the match.
-        */
-       if (res == NULL) return ASL_STATUS_NOT_FOUND;
-       if (res->count == 0)
+       if (l == NULL) return;
+       for (i = 0; l[i] != NULL; i++) free(l[i]);
+       free(l);
+}
+
+/*
+ * Used to sed config parameters.
+ * Line format "= name value"
+ */
+static void
+_parse_set_param(char *s)
+{
+       char **l;
+       uint32_t count;
+
+       if (s == NULL) return;
+       if (s[0] == '\0') return;
+
+       /* skip '=' and whitespace */
+       s++;
+       while ((*s == ' ') || (*s == '\t')) s++;
+
+       l = explode(s, " \t");
+       if (l == NULL) return;
+
+       for (count = 0; l[count] != NULL; count++);
+
+       /* name is required */
+       if (count == 0)
        {
-               aslresponse_free(res);
-               return ASL_STATUS_NOT_FOUND;
+               freeList(l);
+               return;
        }
 
-       out = NULL;
-       status = asl_file_open_write(outfile, 0644, -1, -1, &out);
-       if (status != ASL_STATUS_OK) return status;
+       /* value is required */
+       if (count == 1)
+       {
+               freeList(l);
+               return;
+       }
+
+       if (!strcasecmp(l[0], "aslmanager_debug"))
+       {
+               /* = debug {0|1} */
+               debug = atoi(l[1]);
+       }
+       else if (!strcasecmp(l[0], "store_ttl"))
+       {
+               /* = store_ttl days */
+               ttl = SECONDS_PER_DAY * (time_t)atoll(l[1]);
+       }
+       else if (!strcasecmp(l[0], "max_store_size"))
+       {
+               /* = max_file_size bytes */
+               max_size = atoi(l[1]);
+       }
+       else if (!strcasecmp(l[0], "archive"))
+       {
+               /* = archive {0|1} path */
+               if (!strcmp(l[1], "1"))
+               {
+                       if (l[2] == NULL) archive = PATH_ASL_ARCHIVE;
+                       else archive = strdup(l[2]); /* never freed */
+               }
+               else archive = NULL;
+       }
+       else if (!strcasecmp(l[0], "store_path"))
+       {
+               /* = archive path */
+               store_dir = strdup(l[1]); /* never freed */
+       }
+       else if (!strcasecmp(l[0], "archive_mode"))
+       {
+               archive_mode = strtol(l[1], NULL, 0);
+               if ((archive_mode == 0) && (errno == EINVAL)) archive_mode = 0400;
+       }
 
-       out->flags = ASL_FILE_FLAG_UNLIMITED_CACHE | ASL_FILE_FLAG_PRESERVE_MSG_ID;
+       freeList(l);
+}
 
-       for (i = 0; i < res->count; i++)
+static void
+_parse_line(char *s)
+{
+       if (s == NULL) return;
+       while ((*s == ' ') || (*s == '\t')) s++;
+
+       /*
+        * First non-whitespace char is the rule type.
+        * aslmanager only checks "=" (set parameter) rules.
+        */
+       if (*s == '=') _parse_set_param(s);
+}
+
+char *
+get_line_from_file(FILE *f)
+{
+       char *s, *out;
+       size_t len;
+
+       out = fgetln(f, &len);
+       if (out == NULL) return NULL;
+       if (len == 0) return NULL;
+
+       s = malloc(len + 1);
+       if (s == NULL) return NULL;
+
+       memcpy(s, out, len);
+
+       s[len - 1] = '\0';
+       return s;
+}
+
+static int
+_parse_config_file(const char *name)
+{
+       FILE *cf;
+       char *line;
+
+       cf = fopen(name, "r");
+       if (cf == NULL) return 1;
+
+       while (NULL != (line = get_line_from_file(cf)))
        {
-               mid = 0;
-               status = asl_file_save(out, res->msg[i], &mid);
-               if (status != ASL_STATUS_OK) break;
+               _parse_line(line);
+               free(line);
        }
 
-       asl_file_close(out);
-       return status;
+       fclose(cf);
+
+       return 0;
 }
 
 int
 main(int argc, const char *argv[])
 {
-       int i, bbstrlen, debug;
-       const char *archive, *store_dir;
-       time_t now, best_before, ttl;
+       int i, today_ymd_stringlen, expire_ymd_stringlen;
+       time_t now, ymd_expire;
        struct tm ctm;
-       char bbstr[32], *str, *p;
+       char today_ymd_string[32], expire_ymd_string[32], *str;
        DIR *dp;
        struct dirent *dent;
-       name_list_t *list, *e;
+       name_list_t *ymd_list, *bb_list, *e;
        uint32_t status;
-       size_t file_size, store_size, max_size;
+       size_t file_size, store_size;
        struct stat sb;
 
-       list = NULL;
+       ymd_list = NULL;
+       bb_list = NULL;
 
-       archive = NULL;
-       store_dir = PATH_ASL_STORE;
        ttl = DEFAULT_TTL * SECONDS_PER_DAY;
        max_size = DEFAULT_MAX_SIZE;
        store_size = 0;
@@ -239,12 +416,12 @@ main(int argc, const char *argv[])
        {
                if (!strcmp(argv[i], "-a"))
                {
-                       if (((i + 1) < argc) && (argv[i + 1][0] != '-')) archive = argv[++i];
+                       if (((i + 1) < argc) && (argv[i + 1][0] != '-')) archive = (char *)argv[++i];
                        else archive = PATH_ASL_ARCHIVE;
                }
                else if (!strcmp(argv[i], "-s"))
                {
-                       if (((i + 1) < argc) && (argv[i + 1][0] != '-')) store_dir = argv[++i];
+                       if (((i + 1) < argc) && (argv[i + 1][0] != '-')) store_dir = (char *)argv[++i];
                }
                else if (!strcmp(argv[i], "-ttl"))
                {
@@ -260,6 +437,10 @@ main(int argc, const char *argv[])
                }
        }
 
+       _parse_config_file(_PATH_ASL_CONF);
+
+       if (debug == 1) printf("aslmanager starting\n");
+
        /* check archive */
        if (archive != NULL)
        {
@@ -282,7 +463,7 @@ main(int argc, const char *argv[])
                                {
                                        fprintf(stderr, "aslmanager error: can't create archive %s: %s\n", archive, strerror(errno));
                                        return -1;
-                               }                               
+                               }
                        }
                        else
                        {
@@ -295,93 +476,124 @@ main(int argc, const char *argv[])
 
        chdir(store_dir);
 
-       /* determine current time and time TTL ago */
+       /* determine current time */
        now = time(NULL);
-       best_before = 0;
-       if (ttl > 0) best_before = now - ttl;
 
-       /* construct best before date as YYYY.MM.DD */
+       /* ttl 0 means files never expire */
+       ymd_expire = 0;
+       if (ttl > 0) ymd_expire = now - ttl;
+
+       /* construct today's date as YYYY.MM.DD */
        memset(&ctm, 0, sizeof(struct tm));
-       if (localtime_r((const time_t *)&best_before, &ctm) == NULL) mgr_exit(store_dir, 1);
+       if (localtime_r((const time_t *)&now, &ctm) == NULL) return -1;
 
-       snprintf(bbstr, sizeof(bbstr), "%d.%02d.%02d.", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
-       bbstrlen = strlen(bbstr);
+       snprintf(today_ymd_string, sizeof(today_ymd_string), "%d.%02d.%02d.", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
+       today_ymd_stringlen = strlen(today_ymd_string);
 
-       if (debug == 1) printf("Best Before Date %s\n", bbstr);
+       /* construct regular file expiry date as YYYY.MM.DD */
+       memset(&ctm, 0, sizeof(struct tm));
+       if (localtime_r((const time_t *)&ymd_expire, &ctm) == NULL) return -1;
 
-       dp = opendir(store_dir);
-       if (dp == NULL) mgr_exit(store_dir, 1);
+       snprintf(expire_ymd_string, sizeof(expire_ymd_string), "%d.%02d.%02d.", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
+       expire_ymd_stringlen = strlen(expire_ymd_string);
 
-       /* gather a list of files for dates before the best before date */
+       if (debug == 1) printf("Expiry Date %s\n", expire_ymd_string);
 
+       dp = opendir(store_dir);
+       if (dp == NULL) return -1;
+
+       /* gather a list of YMD files and BB files */
        while ((dent = readdir(dp)) != NULL)
        {
-               if ((dent->d_name[0] < '0') || (dent->d_name[0] > '9')) continue;
-
                memset(&sb, 0, sizeof(struct stat));
                file_size = 0;
                if (stat(dent->d_name, &sb) == 0) file_size = sb.st_size;
-               store_size += file_size;
 
-               list = add_to_list(list, dent->d_name, file_size);
+               if ((dent->d_name[0] >= '0') && (dent->d_name[0] <= '9'))
+               {
+                       ymd_list = add_to_list(ymd_list, dent->d_name, file_size);
+                       store_size += file_size;
+               }
+               else if (!strncmp(dent->d_name, "BB.", 3) && (dent->d_name[3] >= '0') && (dent->d_name[3] <= '9'))
+               {
+                       bb_list = add_to_list(bb_list, dent->d_name, file_size);
+                       store_size += file_size;
+               }
+               else if ((!strcmp(dent->d_name, ".")) || (!strcmp(dent->d_name, "..")))
+               {}
+               else if ((!strcmp(dent->d_name, "StoreData")) || (!strcmp(dent->d_name, "SweepStore")))
+               {}
+               else
+               {
+                       fprintf(stderr, "aslmanager: unexpected file %s in ASL data store\n", dent->d_name);
+               }
        }
 
        closedir(dp);
 
        if (debug == 1)
        {
-               printf("\nData Store Size = %lu\n", store_size);
-               printf("\nData Store Files\n");
-               for (e = list; e != NULL; e = e->next) printf(" %s   %lu\n", e->name, e->size);
+               printf("Data Store Size = %lu\n", store_size);
+               printf("Data Store YMD Files\n");
+               for (e = ymd_list; e != NULL; e = e->next) printf("     %s   %lu\n", e->name, e->size);
+               printf("Data Store BB Files\n");
+               for (e = bb_list; e != NULL; e = e->next) printf("      %s   %lu\n", e->name, e->size);
        }
 
-       /* copy messages in each expired file with ASLExpireTime values to LongTTL files */
-       if (debug == 1) printf("\nStart Scan\n");
+       /* Delete/achive expired YMD files */
+       if (debug == 1) printf("Start YMD Scan\n");
 
-       e = list;
+       e = ymd_list;
        while (e != NULL)
        {
-               if ((store_size <= max_size) && (strncmp(e->name, bbstr, bbstrlen) >= 0)) break;
+               /* stop when a file name/date is after the expire date */
+               if (strncmp(e->name, expire_ymd_string, expire_ymd_stringlen) > 0) break;
 
-               /* find '.' after year */
-               p = strchr(e->name, '.');
-               if (p == NULL) continue;
+               if (archive != NULL)
+               {
+                       str = NULL;
+                       asprintf(&str, "%s/%s", archive, e->name);
+                       if (str == NULL) return -1;
 
-               /* find '.' after month */
-               p++;
-               p = strchr(p, '.');
-               if (p == NULL) continue;
+                       if (debug == 1) printf("  copy %s ---> %s\n", e->name, str);
+                       status = do_copy(e->name, str, archive_mode);
+                       free(str);
+               }
+
+               if (debug == 1) printf("  unlink %s\n", e->name);
+               unlink(e->name);
+
+               store_size -= e->size;
+               e->size = 0;
 
-               /* find '.' after day */
-               p++;
-               p = strchr(p, '.');
-               if (p == NULL) continue;
+               e = e->next;
+       }
 
-               str = NULL;
-               asprintf(&str, "LongTTL%s", p);
-               if (str == NULL) mgr_exit(store_dir, 1);
+       if (debug == 1) printf("Finished YMD Scan\n");
 
-               /* syslog -x [str] -db [e->name] -k ASLExpireTime */
-               if (debug == 1) printf("        scan %s ---> %s\n", e->name, str);
-               else status = do_match(e->name, str, 1, 0);
+       /* Delete/achive expired BB files */
+       if (debug == 1) printf("Start BB Scan\n");
 
-               free(str);
-               str = NULL;
+       e = bb_list;
+       while (e != NULL)
+       {
+               /* stop when a file name/date is after the expire date */
+               if (strncmp(e->name + 3, today_ymd_string, today_ymd_stringlen) > 0) break;
 
                if (archive != NULL)
                {
                        str = NULL;
                        asprintf(&str, "%s/%s", archive, e->name);
-                       if (str == NULL) mgr_exit(store_dir, 1);
+                       if (str == NULL) return -1;
 
-                       /* syslog -x [str] -db [e->name] */
-                       if (debug == 1) printf("        copy %s ---> %s\n", e->name, str);
-                       else status = do_match(e->name, str, 0, 0);
+                       /* syslog -x [str] -f [e->name] */
+                       if (debug == 1) printf("  copy %s ---> %s\n", e->name, str);
+                       status = do_copy(e->name, str, archive_mode);
                        free(str);
                }
 
-               if (debug == 1) printf("        unlink %s\n", e->name);
-               else unlink(e->name);
+               if (debug == 1) printf("  unlink %s\n", e->name);
+               unlink(e->name);
 
                store_size -= e->size;
                e->size = 0;
@@ -389,60 +601,80 @@ main(int argc, const char *argv[])
                e = e->next;
        }
 
-       if (debug == 1)
-       {
-               printf("Finished Scan\n");
-               printf("\nData Store Size = %lu\n", store_size);
-       }
-
-       free_list(list);
-       list = NULL;
+       if (debug == 1) printf("Finished BB Scan\n");
 
-       dp = opendir(PATH_ASL_STORE);
-       if (dp == NULL) mgr_exit(store_dir, 1);
+       /* if data store is over max_size, delete/archive more YMD files */
+       if ((debug == 1) && (store_size > max_size)) printf("Additional YMD Scan\n");
+       
+       e = ymd_list;
+       while ((e != NULL) && (store_size > max_size))
+       {
+               if (e->size != 0)
+               {
+                       /* stop when we get to today's files */
+                       if (strncmp(e->name, today_ymd_string, today_ymd_stringlen) == 0) break;
 
-       /* gather a list of LongTTL files */
+                       if (archive != NULL)
+                       {
+                               str = NULL;
+                               asprintf(&str, "%s/%s", archive, e->name);
+                               if (str == NULL) return -1;
+
+                               /* syslog -x [str] -f [e->name] */
+                               if (debug == 1) printf("  copy %s ---> %s\n", e->name, str);
+                               status = do_copy(e->name, str, archive_mode);
+                               free(str);
+                       }
 
-       while ((dent = readdir(dp)) != NULL)
-       {
-               if (!strncmp(dent->d_name, "LongTTL.", 8)) list = add_to_list(list, dent->d_name, 0);
-       }
+                       if (debug == 1) printf("  unlink %s\n", e->name);
+                       unlink(e->name);
 
-       closedir(dp);
+                       store_size -= e->size;
+                       e->size = 0;
+               }
 
-       if (debug == 1)
-       {
-               printf("\nData Store LongTTL Files\n");
-               for (e = list; e != NULL; e = e->next) printf(" %s\n", e->name);
+               e = e->next;
        }
 
-       if (debug == 1) printf("\nScan for expired messages\n");
+       /* if data store is over max_size, delete/archive more BB files */
+       if ((debug == 1) && (store_size > max_size)) printf("Additional BB Scan\n");
 
-       e = list;
-       while (e != NULL)
+       e = bb_list;
+       while ((e != NULL) && (store_size > max_size))
        {
-               /* syslog -x LongTTL.new -db [e->name] -k ASLExpireTime Nge [now] */
-               if (debug == 1)
-               {
-                       printf("        %s\n", e->name);
-               }
-               else
+               if (e->size != 0)
                {
-                       status = do_match(e->name, "LongTTL.new", 1, now);
+                       if (archive != NULL)
+                       {
+                               str = NULL;
+                               asprintf(&str, "%s/%s", archive, e->name);
+                               if (str == NULL) return -1;
+
+                               /* syslog -x [str] -f [e->name] */
+                               if (debug == 1) printf("  copy %s ---> %s\n", e->name, str);
+                               status = do_copy(e->name, str, archive_mode);
+                               free(str);
+                       }
+
+                       if (debug == 1) printf("  unlink %s\n", e->name);
                        unlink(e->name);
-                       if (status == ASL_STATUS_OK) rename("LongTTL.new", e->name);
+
+                       store_size -= e->size;
+                       e->size = 0;
                }
 
                e = e->next;
        }
 
-       if (debug == 1) printf("Finished scan for expired messages\n");
+       free_list(ymd_list);     
+       free_list(bb_list);
 
-       free_list(list);
-       list = NULL;
+       if (debug == 1)
+       {
+               printf("Data Store Size = %lu\n", store_size);
+               printf("aslmanager finished\n");
+       }
 
-       mgr_exit(store_dir, 0);
-       /* UNREACHED */
        return 0;
 }