/* * Copyright (c) 2009-2010 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern time_t asl_parse_time(const char *); #define TEMP_NAME "_TMP_.asl" #define STORE_DATA_FLAGS 0x00000000 static const char *store_path = PATH_ASL_STORE; /* * Cache the output file for BB writes. * we write messages in the order in which they were generated, * so we are almost guaranteed to use the cache in most cases. */ static asl_file_t *cache_file = NULL; static uid_t cache_uid = -1; static uid_t cache_gid = -1; static time_t cache_bb = 0; typedef struct name_list_s { char *name; struct name_list_s *next; } name_list_t; static name_list_t * add_to_list(name_list_t *l, const char *name) { name_list_t *e, *x; if (name == NULL) return l; e = (name_list_t *)calloc(1, sizeof(name_list_t)); if (e == NULL) return NULL; e->name = strdup(name); if (e->name == NULL) { free(e); return NULL; } /* list is sorted by name (i.e. primarily by timestamp) */ if (l == NULL) return e; if (strcmp(e->name, l->name) <= 0) { e->next = l; return e; } for (x = l; (x->next != NULL) && (strcmp(e->name, x->next->name) > 0) ; x = x->next); e->next = x->next; x->next = e; return l; } static void free_list(name_list_t *l) { name_list_t *e; while (l != NULL) { e = l; l = l->next; free(e->name); free(e); } free(l); } /* find all messages that have an ASLExpireTime key */ static uint32_t do_ASLExpireTime_search(asl_store_t *s, asl_search_result_t **out) { asl_search_result_t q, *query, *res; asl_msg_t *qm[1]; uint32_t status; uint64_t mid; qm[0] = asl_msg_new(ASL_TYPE_QUERY); if (qm[0] == NULL) return ASL_STATUS_NO_MEMORY; q.count = 1; q.curr = 0; q.msg = qm; query = &q; if (asl_msg_set_key_val_op(qm[0], ASL_KEY_EXPIRE_TIME, NULL, ASL_QUERY_OP_TRUE) != 0) { asl_msg_release(qm[0]); return ASL_STATUS_NO_MEMORY; } res = NULL; mid = 0; status = asl_store_match(s, query, out, &mid, 0, 0, 1); asl_msg_release(qm[0]); return status; } /* remove all messages that have an ASLExpireTime key */ static uint32_t do_ASLExpireTime_filter(const char *name) { aslmsg msg; asl_file_t *in, *out; uint32_t status; uint64_t mid; char *inpath, *outpath; struct stat sb; if (name == NULL) return ASL_STATUS_INVALID_ARG; in = NULL; inpath = NULL; asprintf(&inpath, "%s/%s", store_path, name); if (inpath == NULL) return ASL_STATUS_NO_MEMORY; memset(&sb, 0, sizeof(struct stat)); if (stat(inpath, &sb) < 0) { free(inpath); return ASL_STATUS_INVALID_STORE; } status = asl_file_open_read(inpath, &in); if (status != ASL_STATUS_OK) { free(inpath); return ASL_STATUS_OK; } out = NULL; outpath = NULL; asprintf(&outpath, "%s/%s", store_path, TEMP_NAME); if (outpath == NULL) { asl_file_close(in); free(inpath); return ASL_STATUS_NO_MEMORY; } status = asl_file_open_write(outpath, sb.st_mode, sb.st_uid, sb.st_gid, &out); if (status != ASL_STATUS_OK) { asl_file_close(in); free(inpath); free(outpath); return status; } out->flags = ASL_FILE_FLAG_PRESERVE_MSG_ID; msg = NULL; while (asl_file_fetch_next(in, &msg) == ASL_STATUS_OK) { if (msg == NULL) break; mid = 0; if (asl_get(msg, ASL_KEY_EXPIRE_TIME) == NULL) status = asl_file_save(out, msg, &mid); asl_free(msg); msg = NULL; if (status != ASL_STATUS_OK) break; } asl_file_close(in); asl_file_close(out); unlink(inpath); rename(outpath, inpath); free(inpath); free(outpath); return status; } /* qsort compare function for sorting by message ID */ static int sort_compare(const void *a, const void *b) { const char *va, *vb; uint64_t na, nb; va = asl_get(*(aslmsg *)a, ASL_KEY_MSG_ID); vb = asl_get(*(aslmsg *)b, ASL_KEY_MSG_ID); if (va == NULL) return -1; if (vb == NULL) return 1; na = atoll(va); nb = atoll(vb); if (na < nb) return -1; if (na > nb) return 1; return 0; } /* save a message to an appropriately named BB file */ static uint32_t save_bb_msg(aslmsg msg) { const char *val; uid_t u, ruid; gid_t g, rgid; struct tm ctm; time_t msg_time, bb; char *path, *tstring; asl_file_t *out; uint64_t mid; mode_t m; uint32_t status; if (msg == NULL) return ASL_STATUS_OK; val = asl_get(msg, ASL_KEY_EXPIRE_TIME); if (val == NULL) return ASL_STATUS_INVALID_ARG; msg_time = asl_parse_time(val); val = asl_get(msg, ASL_KEY_READ_UID); ruid = -1; if (val != NULL) ruid = atoi(val); val = asl_get(msg, ASL_KEY_READ_GID); rgid = -1; if (val != NULL) rgid = atoi(val); if (localtime_r((const time_t *)&msg_time, &ctm) == NULL) return ASL_STATUS_FAILED; /* * This supports 12 monthy "Best Before" buckets. * We advance the actual expiry time to day zero of the following month. * mktime() is clever enough to know that you actually mean the last day * of the previous month. What we get back from localtime is the last * day of the month in which the message expires, which we use in the name. */ ctm.tm_sec = 0; ctm.tm_min = 0; ctm.tm_hour = 0; ctm.tm_mday = 0; ctm.tm_mon += 1; bb = mktime(&ctm); u = 0; g = 0; if (ruid != -1) u = ruid; if (rgid != -1) g = rgid; out = NULL; if (cache_file != NULL) { if ((cache_uid == u) && (cache_gid == g) && (cache_bb == bb)) { out = cache_file; } else { asl_file_close(cache_file); cache_file = NULL; cache_uid = -1; cache_gid = -1; cache_bb = 0; } } if (out == NULL) { if (localtime_r((const time_t *)&bb, &ctm) == NULL) return ASL_STATUS_FAILED; tstring = NULL; asprintf(&tstring, "%s/BB.%d.%02d.%02d", store_path, ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday); if (tstring == NULL) return ASL_STATUS_NO_MEMORY; path = NULL; m = 0644; if (ruid == -1) { if (rgid == -1) { asprintf(&path, "%s.asl", tstring); } else { m = 0640; asprintf(&path, "%s.G%d.asl", tstring, g); } } else { if (rgid == -1) { m = 0600; asprintf(&path, "%s.U%d.asl", tstring, u); } else { m = 0640; asprintf(&path, "%s.U%d.G%u.asl", tstring, u, g); } } if (path == NULL) return ASL_STATUS_NO_MEMORY; status = asl_file_open_write(path, m, u, g, &out); free(path); if (status != ASL_STATUS_OK) return status; if (out == NULL) return ASL_STATUS_FAILED; out->flags = ASL_FILE_FLAG_PRESERVE_MSG_ID; cache_file = out; cache_uid = u; cache_gid = g; cache_bb = bb; } status = asl_file_save(out, msg, &mid); return status; } static uint32_t finish_conversion() { FILE *sd; uint32_t store_flags; int status; char *path; path = NULL; asprintf(&path, "%s/%s", store_path, FILE_ASL_STORE_DATA); sd = fopen(path, "a"); free(path); if (sd == NULL) return ASL_STATUS_WRITE_FAILED; store_flags = STORE_DATA_FLAGS; status = fwrite(&store_flags, sizeof(uint32_t), 1, sd); fclose(sd); if (status != 1) return ASL_STATUS_WRITE_FAILED; return ASL_STATUS_OK; } /* * Utility to convert a data store with LongTTL files into * a store with Best Before files. * * Returns quickly if the data store has already been converted. * * Older versions of the data store included messages with non-standard time-to-live * records in the daily data files (yyyy.mm.dd.asl). When the files expired, aslmanager * first copied messages with ASLExpireTime keys to a LongTTL file, then deleted the * original data file. * * We now write ASLExpireTime messages to a Best Before file (BB.yyyy.mm.dd.asl) * and aslmanager just deletes these files after the Best Before date has passed. * * If StoreData is bigger than 8 bytes, the store has been converted. Do nothing. * * Convert the store: * Search the store for messages that have an ASLExpireTime. * Sort by ASLMessageID * Remove all BB.* files and all LongTTL.* files * Write the ASLExpireTime messages into a new set of BB files * Re-write each YMD file without messages that have an ASLExpireTime * Add a new 4-byte flags field to StoreData */ uint32_t bb_convert(const char *name) { struct stat sb; asl_store_t *store; uint32_t status; asl_search_result_t *expire_time_records; DIR *dp; struct dirent *dent; int i; name_list_t *list, *e; char *path; if (name != NULL) store_path = name; /* StoreData must exist */ path = NULL; asprintf(&path, "%s/%s", store_path, FILE_ASL_STORE_DATA); if (path == NULL) return ASL_STATUS_NO_MEMORY; memset(&sb, 0, sizeof(struct stat)); i = stat(path, &sb); free(path); if (i != 0) return ASL_STATUS_INVALID_STORE; /* must be a regular file */ if (!S_ISREG(sb.st_mode)) return ASL_STATUS_INVALID_STORE; /* check is the store has already been converted */ if (sb.st_size > sizeof(uint64_t)) return ASL_STATUS_OK; /* find ASLExpireTime messages */ status = asl_store_open_read(store_path, &store); if (status != ASL_STATUS_OK) return status; expire_time_records = NULL; status = do_ASLExpireTime_search(store, &expire_time_records); asl_store_close(store); if (status != ASL_STATUS_OK) return status; /* unlink BB.* and LongTTL.* */ dp = opendir(store_path); if (dp == NULL) return ASL_STATUS_READ_FAILED; while ((dent = readdir(dp)) != NULL) { if ((!strncmp(dent->d_name, "BB.", 3)) || (!strncmp(dent->d_name, "LongTTL.", 8))) { path = NULL; asprintf(&path, "%s/%s", store_path, dent->d_name); if (path == NULL) { closedir(dp); return ASL_STATUS_NO_MEMORY; } unlink(path); free(path); } } closedir(dp); if ((expire_time_records == NULL) || (expire_time_records->count == 0)) return finish_conversion(); /* sort by ASLMessageID */ qsort(expire_time_records->msg, expire_time_records->count, sizeof(aslmsg), sort_compare); /* save the ASLExpireTime messages into a new set of BB files */ for (i = 0; i < expire_time_records->count; i++) { status = save_bb_msg((aslmsg)expire_time_records->msg[i]); if (status != ASL_STATUS_OK) { if (cache_file != NULL) asl_file_close(cache_file); return status; } } if (cache_file != NULL) asl_file_close(cache_file); aslresponse_free(expire_time_records); /* Re-write each YMD file without messages that have an ASLExpireTime */ dp = opendir(store_path); if (dp == NULL) return ASL_STATUS_READ_FAILED; list = NULL; while ((dent = readdir(dp)) != NULL) { if ((dent->d_name[0] < '0') || (dent->d_name[0] > '9')) continue; list = add_to_list(list, dent->d_name); } closedir(dp); for (e = list; e != NULL; e = e->next) { status = do_ASLExpireTime_filter(e->name); if (status != ASL_STATUS_OK) { free_list(list); return status; } } free_list(list); return finish_conversion(); }