/* * PROGRAM: JRD Message Facility * MODULE: build_file.epp * DESCRIPTION: Build message file * * The contents of this file are subject to the Interbase Public * License Version 1.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy * of the License at http://www.Inprise.com/IPL.html * * Software distributed under the License is distributed on an * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express * or implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code was created by Inprise Corporation * and its predecessors. Portions created by Inprise Corporation are * Copyright (C) Inprise Corporation. * * All Rights Reserved. * Contributor(s): ______________________________________. * * 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "XENIX" port * 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "DELTA" port * * 2002.10.30 Sean Leyne - Removed support for obsolete "PC_PLATFORM" define * */ #include "firebird.h" #include #include "../jrd/common.h" #include #include #include "../jrd/ibase.h" #include "../jrd/common.h" #include "../jrd/msg.h" #include "../jrd/gds_proto.h" #ifdef HAVE_UNISTD_H #include #endif const int max_levels = 4; typedef msgnod* msgnod_ptr_array[max_levels]; DATABASE DB = "msg.fdb"; static void ascii_str_to_upper(TEXT*); static char* copy_terminate(char* dest, const char* src, size_t bufsize); static USHORT do_msgs(const TEXT*, const TEXT*, bool); static void propagate(msgnod**, msgnod**, ULONG, ULONG); static SLONG write_bucket(const msgnod*, USHORT); static void sanitize(TEXT*); static SLONG global_file_position; static int global_file; /* keep the LOCALE_PAT names in sync with file_params.h */ // CVC: It's #define MSG_FILE_LANG "intl/%.8s.msg" // in jrd/file_params.h, it seems we are already out of sync! #ifdef WIN_NT #include // open, lseek, write, close const char path_separator = '\\'; #else const char path_separator = '/'; #endif const char* const locale_pattern = "%.10s.msg"; const char* const FILENAME = "firebird.msg"; #include #ifndef WIN_NT #include #endif #ifndef O_RDWR #include #endif #ifndef O_BINARY #define O_BINARY 0 #endif int CLIB_ROUTINE main( int argc, char** argv) { /************************************** * * m a i n * ************************************** * * Functional description * Top level routine. * **************************************/ TEXT db_file[MAXPATHLEN], filename[MAXPATHLEN], pathbuffer[MAXPATHLEN]; BASED ON LOCALES.LOCALE this_locale; strcpy(db_file, "msg.fdb"); strcpy(filename, FILENAME); TEXT* pathname = NULL; TEXT* locale = NULL; bool sw_warning = false; const TEXT* const* const end_args = argv + argc; for (++argv; argv < end_args;) { const TEXT* p = *argv++; bool sw_bad = false; if (*p != '-') sw_bad = true; else { switch (UPPER(p[1])) { case 'D': if (argv >= end_args) sw_bad = true; else copy_terminate(db_file, *argv++, MAXPATHLEN); break; case 'F': if (argv >= end_args) sw_bad = true; else copy_terminate(filename, *argv++, MAXPATHLEN); break; case 'P': if (argv >= end_args) sw_bad = true; else { copy_terminate(pathbuffer, *argv++, MAXPATHLEN); pathname = pathbuffer; } break; case 'L': if (argv >= end_args) sw_bad = true; else locale = *argv++; break; case 'W': sw_warning = true; break; default: sw_bad = true; } } if (sw_bad) { printf("Invalid option \"%s\". Valid options are:\n", p); printf("\t-D\tDatabase name\n"); printf("\t-F\tMessage file name\n"); printf("\t-P\tMessage file path (should end with '/')\n"); printf("\t-L\tLocale name\n"); printf("\t-W\tVerbose warning messages\n"); exit(FINI_ERROR); } } /* Get db_file ready to go */ READY db_file AS DB; START_TRANSACTION; /* make sure the path name ends in a '/' */ if (pathname) { const size_t len = strlen(pathname); //fb_assert(len + 1 < MAXPATHLEN); if (pathname[len - 1] != path_separator) { pathname[len] = path_separator; pathname[len + 1] = 0; } } const size_t separator_pos = pathname ? strlen(pathname) : 0; /* check for the locale option */ if (!locale) { // no locale given: do the regular US msgs if (!pathname) do_msgs(filename, NULL, false); else { //fb_assert(strlen(pathname) + strlen(filename) < MAXPATHLEN); strcat(pathname, filename); do_msgs(pathname, NULL, false); } } else { bool got_one = false; FOR FIRST 1 L IN LOCALES WITH L.LOCALE = locale got_one = true; END_FOR; if (got_one) { // do only 1 locale strcpy(this_locale, locale); sanitize(locale); if (pathname) { sprintf(filename, locale_pattern, locale); strcat(pathname, filename); do_msgs(pathname, this_locale, sw_warning); } else do_msgs(filename, this_locale, sw_warning); } else { strncpy(this_locale, locale, sizeof(this_locale)); ascii_str_to_upper(this_locale); if (!strcmp(this_locale, "ALL")) { FOR LC IN LOCALES // do all locales except piglatin WITH LC.LOCALE != 'pg_PG' AND LC.LOCALE != 'piglatin'; locale = LC.LOCALE; strcpy(this_locale, LC.LOCALE); printf("build_file: building locale %s", this_locale); sanitize(locale); sprintf(filename, locale_pattern, locale); if (pathname) { strcat(pathname, filename); printf(" to file %s\n", pathname); do_msgs(pathname, this_locale, sw_warning); pathname[separator_pos] = 0; // for the next iteration. } else { printf(" to file %s\n", filename); do_msgs(filename, this_locale, sw_warning); } END_FOR; } else { printf("build_file: Unknown locale: %s\n", locale); printf("Valid options are:\n"); FOR LO IN LOCALES printf("\t%s\n", LO.LOCALE); END_FOR; printf("\tall\n"); COMMIT; FINISH DB; exit(FINI_ERROR); } } } COMMIT; FINISH; return(FINI_OK); } static void ascii_str_to_upper( TEXT* s) { /************************************** * * a s c i i _ s t r _ t o _ u p p e r * ************************************** * * Functional description * change the a - z chars in string to upper case * **************************************/ while (*s) { *s >= 'a' && *s <= 'z' ? *s &= 0xDF : *s; *s++; } } static char* copy_terminate(char* dest, const char* src, size_t bufsize) { /************************************** * * c o p y _ t e r m i n a t e * ************************************** * * Functional description * Do the same as strncpy but ensure the null terminator is written. * To avoid putting here #include "../common/utils_proto.h" * **************************************/ if (!bufsize) // Was it a joke? return dest; --bufsize; strncpy(dest, src, bufsize); dest[bufsize] = 0; return dest; } static USHORT do_msgs( const TEXT* filename, const TEXT* locale, bool sw_warning) { /************************************** * * d o _ m s g s * ************************************** * * Functional description * Build the message file * **************************************/ // Divy up memory among various buffers // Memory leaking until the program finishes? msgrec* leaf_node = (msgrec*) gds__alloc((SLONG) MSG_BUCKET); msgrec* const leaf = leaf_node; const TEXT* const end_leaf = (TEXT*) leaf + MSG_BUCKET; // Open output file global_file = open(filename, O_WRONLY | O_CREAT | O_BINARY, 0666); if (global_file == -1) { printf("Can't open %s\n", filename); return FINI_ERROR; } global_file_position = 0; // Format and write header isc_msghdr header; header.msghdr_major_version = MSG_MAJOR_VERSION; header.msghdr_minor_version = MSG_MINOR_VERSION; header.msghdr_bucket_size = MSG_BUCKET; // CVC: Since this is an unused field that holds garbage in the *.msg file, // we'll initialize it to the date the FB project was registered with SF. header.msghdr_origin = 20000730; // 2000-07-30 write_bucket((msgnod*) &header, sizeof(header)); // Write out messages building B-tree int warning_counter = 0; USHORT n = 0; ULONG position = 0, prior_code = 0; TEXT msg_text[256]; msgnod_ptr_array buckets, nodes; nodes[0] = NULL; size_t len = 0; FOR X IN MESSAGES SORTED BY X.CODE // pre-load with US English message - just in case we don't // have a translation available. strcpy(msg_text, X.TEXT); len = strlen(msg_text); if (locale) { // want translated output // Overwrite English message with translation, if we find one int found_one = 0; FOR Y IN TRANSMSGS WITH Y.FAC_CODE = X.FAC_CODE AND Y.NUMBER = X.NUMBER AND Y.LOCALE = locale AND Y.TEXT NOT MISSING AND Y.TEXT != ' '; strcpy(msg_text, Y.TEXT); len = strlen(msg_text); found_one++; END_FOR; if (!found_one && sw_warning) printf ("build_file: Warning - no %s translation of msg# %"QUADFORMAT"d\n", locale, X.CODE); if (found_one > 1 && sw_warning) printf ("build_file: Warning - multiple %s translations of msg# %"QUADFORMAT"d\n", locale, X.CODE); if (found_one != 1) warning_counter++; } if (leaf_node->msgrec_text + len >= end_leaf) { position = write_bucket((msgnod*) leaf, n); propagate(buckets, nodes, prior_code, position); leaf_node = leaf; } leaf_node->msgrec_code = prior_code = MSG_NUMBER(X.FAC_CODE, X.NUMBER); leaf_node->msgrec_length = len; // Let's not store trash in flags. leaf_node->msgrec_flags = X.FLAGS.NULL ? 0 : X.FLAGS; //n = OFFSETA(MSGREC, msgrec_text) + len; // useless? See assignment below. TEXT* p = leaf_node->msgrec_text; memcpy(p, msg_text, len); n = p + len - (SCHAR*) leaf; // For the next iteration. leaf_node = NEXT_LEAF(leaf_node); END_FOR; // Write a high water mark on leaf node if (leaf_node->msgrec_text + len >= end_leaf) { n = (SCHAR *) leaf_node - (SCHAR *) leaf; position = write_bucket((msgnod*) leaf, n); propagate(buckets, nodes, prior_code, position); leaf_node = leaf; } leaf_node->msgrec_code = ~0; leaf_node->msgrec_length = 0; leaf_node->msgrec_flags = 0; n = (SCHAR *) leaf_node - (SCHAR *) leaf; position = write_bucket((msgnod*) leaf, n); // Finish off upper levels of tree header.msghdr_levels = 1; for (msgnod** ptr = nodes, **ptr2 = buckets; *ptr; ptr++, ptr2++) { msgnod* node = *ptr; node->msgnod_code = ~0; node->msgnod_seek = position; n = (SCHAR *) (node + 1) - (SCHAR *) * ptr2; position = write_bucket(*ptr2, n); ++header.msghdr_levels; } header.msghdr_top_tree = position; /* Re-write header record and finish */ lseek(global_file, LSEEK_OFFSET_CAST 0, 0); write(global_file, &header, sizeof(header)); close(global_file); global_file = -1; if (warning_counter) printf("build_file: %d messages lack translations in locale %s\n", warning_counter, locale); return FINI_OK; } static void propagate(msgnod** buckets, msgnod** nodes, ULONG prior_code, ULONG position) { /************************************** * * p r o p a g a t e * ************************************** * * Functional description * Propagate a full bucket upward. * **************************************/ /* Make sure current level has been allocated */ if (!*nodes) { *nodes = *buckets = (msgnod*) gds__alloc((SLONG) MSG_BUCKET); nodes[1] = NULL; } /* Insert into current bucket */ msgnod* node = (*nodes)++; node->msgnod_code = prior_code; node->msgnod_seek = position; /* Check for full bucket. If not, we're done */ const msgnod* const end = (msgnod*) ((SCHAR*) *buckets + MSG_BUCKET); if (*nodes < end) return; /* Bucket is full -- write it out, propagate the split, and re-initialize */ position = write_bucket(*buckets, MSG_BUCKET); propagate(buckets + 1, nodes + 1, prior_code, position); *nodes = *buckets; } static SLONG write_bucket( const msgnod* bucket, USHORT length) { /************************************** * * w r i t e _ b u c k e t * ************************************** * * Functional description * Write stuff, return position of stuff written. * **************************************/ const USHORT padded_length = ROUNDUP(length, sizeof(SLONG)); const SLONG position = global_file_position; int n = write(global_file, bucket, length); if (n == -1) { fprintf(stderr, "IO error on write()\n"); exit(FINI_ERROR); } const SLONG zero_bytes = 0; n = write(global_file, &zero_bytes, padded_length - length); if (n == -1) { fprintf(stderr, "IO error on write()\n"); exit(FINI_ERROR); } global_file_position += padded_length; return position; } static void sanitize( TEXT* locale) { /************************************** * * s a n i t i z e * ************************************** * * Functional description * Clean up a locale to make it acceptable for use in file names * for Windows NT: replace any period with '_' * Keep this in sync with gds.cpp * **************************************/ while (*locale) { if (*locale == '.') *locale = '_'; locale++; } }