// // aegis - project change supervisor // Copyright (C) 2005, 2006, 2008 Peter Miller // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see // . // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static nstring build_temp_file_name(const nstring &path) { nstring dirname = os_dirname_relative(path); nstring base = os_basename(path); return dirname + "/.tmp." + base; } static nstring get_user_name() { int uid, gid, umsk; os_become_query(&uid, &gid, &umsk); struct passwd *pw = getpwuid_cached(uid); if (pw) return pw->pw_name; return nstring::format("uid:%d", uid); } void simple_version_tool::checkin(const nstring &input_file_name) { rfc822 meta_data; checkin(input_file_name, meta_data); } void simple_version_tool::checkin(const nstring &input_file_name, const rfc822 &meta_data) { rfc822_functor_version_previous prev; bool there_are_previous_versions = !list(prev); nstring version; if (meta_data.is_set("version")) { version = meta_data.get("version"); if (version.empty()) { sub_context_ty sc; sc.var_set_string("File_Name", history_file_name); sc.var_set_string("Number", version); sc.fatal_intl ( i18n("$filename: version $number invalid") ); // NOTREACHED } } else { if (there_are_previous_versions) { // // If the old version ends with digits, add one to the // digits and preserve the prefix. // nstring old_version = prev.get_result(); const char *start = old_version.c_str(); const char *end = start + old_version.size(); const char *cp = end; while (cp > old_version.c_str() && isdigit((unsigned char)cp[-1])) --cp; if (cp < end) { int n = atoi(cp); char buffer[20]; snprintf(buffer, sizeof(buffer), "%d", n + 1); version = nstring(start, cp - start) + nstring(buffer); } } } if (version.empty()) { // // When all else fails, start from one. // version = "1"; } // // Make sure the new version number (or whatever it is) will be unique. // rfc822_functor_version_search snoopy(version); list(snoopy); if (snoopy.get_result()) { sub_context_ty sc; sc.var_set_string("File_Name", history_file_name); sc.var_set_string("Number", version); sc.fatal_intl(i18n("$filename: version $number already exists")); // NOTREACHED } // // We have to remember to unlink the temporary file if something // goes wrong. (The temporary file is not in /tmp because we want // to do a simple rename at the end, and that has to be on the same // file system.) // nstring temp_file_name = build_temp_file_name(history_file_name); quit_register(*new quit_action_unlink(temp_file_name)); // // Open the input file. // // We will need some information about it to put in the header. // input in = new input_file(input_file_name); // // Build the header to write to the file. // rfc822 header(meta_data); header.set("content-type", os_magic_file(input_file_name)); header.set("checksum", calculate_adler32(input_file_name)); header.set("version", version); header.set("content-length", in->length()); header.set("content-transfer-encoding", nstring("8bit")); // // These things are merely defaults in the case where the user does // not define any corresponding meta-data herself. // if (!header.is_set("date")) header.set("date", rfc822::date()); if (!header.is_set("user")) header.set("user", get_user_name()); // // We have to scope the output instance, so that the output is all // written and closed before we do the rename. // { output::pointer os = output_file::open(temp_file_name); switch (compression_algorithm) { case compression_algorithm_none: break; case compression_algorithm_gzip: os = output_gzip::create(os); break; #ifndef DEBUG default: #endif case compression_algorithm_unspecified: case compression_algorithm_not_set: // We will default to the best compression available. // For now, that means bzip2 case compression_algorithm_bzip2: os = output_bzip2::create(os); break; } // // Each version consists of an RFC 822 header, and the // file contents, which can be binary. The header has a // Content-Length entry, so we know exactly how long the content // is. This means we can skip down the file versions when they // are simply placed end-to-end. // // Note that if the source file is plain text, then the // (uncompressed) output will be plain text, too. // header.store(os); os << in; if (there_are_previous_versions) { // // Add the previous history on the end of the new file. // These previous versions should compress *very* well, // because thay are usually almost the same as the new // version. Thus the bzip2 compression tables will apply, // and provide reasonable compression. // // Note: gzip resets its tables every 32KB, so it's only // good for files smaller than 16KB per version. The bzip2 // algorithm resets its tables every 900KB, so it's good // for file sizes up to 450KB per version. Both algorithms // keep working beyond these limits, but you don't see the // excellent results that are obtain for smaller source // files. // input old_hist_uncom = new input_file(history_file_name); // Check both algorithms for decompression, in case we are // looking at an older archive. input temp = input_gunzip_open(old_hist_uncom); input old_history_file = input_bunzip2_open(temp); os << old_history_file; } } // // Now that we have the new file contents, we can replace the old // file with the new file. (This does the unlink of the old file, // as well.) // os_rename(temp_file_name, history_file_name); }