/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ module; #include #include #include #include #include #include #include #include #include #include #include #include #include #include export module katja.slackpkg; import katja.utils; import katja.pkgtools; export namespace katja { class Slackpkg final : public Pkgtools { public: /** * katja::Slackpkg::Slackpkg: * @name: Repository name. * @mirror: Repository mirror. * @order: Repository order. * @blacklist: Blacklist. * @priority: Groups priority. * * Constructor. * * Returns: New #katja::Slackpkg. **/ Slackpkg(const std::string& name, const std::string& mirror, std::uint8_t order, const char *blacklist, char **priority) noexcept : Pkgtools(name, mirror, order) { if (blacklist != nullptr) { this->blacklist = std::regex(blacklist); } if (priority != nullptr) { for (char **cur_priority = priority; *cur_priority; cur_priority++) { this->priority.emplace_back(*cur_priority); } } } /** * katja::Slackpkg::collect_cache_info: * @tmpl: temporary directory for downloading the files. * * Download files needed to get the information like the list of packages * in available repositories, updates, package descriptions and so on. * * Returns: List of files needed for building the cache. **/ std::forward_list collect_cache_info(const std::filesystem::path& tmpl) noexcept { CURL *curl = nullptr; cache_entry source_dest; std::forward_list file_list; /* Create the temporary directory for the repository */ std::filesystem::path tmp_dir = tmpl / this->name; std::filesystem::create_directories(tmp_dir); /* Download PACKAGES.TXT. These files are most important, break if some of them couldn't be found */ for (const std::string& current_priority : this->priority) { source_dest.first = this->mirror + current_priority + "/PACKAGES.TXT"; source_dest.second = tmp_dir / "PACKAGES.TXT"; if (get_file(&curl, source_dest.first.c_str(), std::nullopt) == CURLE_OK) { file_list.emplace_front(std::move(source_dest)); } else { goto out; } /* Download file lists if available */ source_dest.first = this->mirror + current_priority + "/MANIFEST.bz2"; source_dest.second = tmpl / this->name / (current_priority + "-MANIFEST.bz2"); if (get_file(&curl, source_dest.first.c_str(), std::nullopt) == CURLE_OK) { file_list.emplace_front(std::move(source_dest)); } } out: if (curl) { curl_easy_cleanup(curl); } return file_list; } /** * katja::Slackpkg::generate_cache: * @job_data: A #JobData. * @tmpl: temporary directory for downloading the files. * * Download files needed to get the information like the list of packages * in available repositories, updates, package descriptions and so on. * * Returns: List of files needed for building the cache. **/ void generate_cache(JobData *job_data, const std::filesystem::path& tmpl) noexcept { std::vector pkg_tokens; char *query = nullptr; unsigned pkg_compressed = 0, pkg_uncompressed = 0; std::uint8_t pkg_name_len; std::string desc, line, location; sqlite3_stmt *insert_statement = nullptr, *update_statement = nullptr, *insert_default_statement = nullptr; sqlite3_stmt *statement; std::optional filename, summary; /* Check if the temporary directory for this repository exists, then the file metadata have to be generated */ std::filesystem::path packages_txt = tmpl / this->name / "PACKAGES.TXT"; std::ifstream data_in(packages_txt); if (!data_in) { goto out; } /* Remove the old entries from this repository */ if (sqlite3_prepare_v2(job_data->db, "DELETE FROM repos WHERE repo LIKE @repo", -1, &statement, nullptr) == SQLITE_OK) { sqlite3_bind_text(statement, 1, this->name.c_str(), -1, SQLITE_TRANSIENT); sqlite3_step(statement); sqlite3_finalize(statement); } if (sqlite3_prepare_v2(job_data->db, "INSERT INTO repos (repo_order, repo) VALUES (@repo_order, @repo)", -1, &statement, nullptr) != SQLITE_OK) { goto out; } sqlite3_bind_int(statement, 1, this->order); sqlite3_bind_text(statement, 2, this->name.c_str(), -1, SQLITE_TRANSIENT); sqlite3_step(statement); sqlite3_finalize(statement); /* Insert new records */ if ((sqlite3_prepare_v2(job_data->db, "INSERT OR REPLACE INTO pkglist (full_name, ver, arch, ext, location, " "summary, desc, compressed, uncompressed, name, repo_order, cat) " "VALUES (@full_name, @ver, @arch, @ext, @location, @summary, " "@desc, @compressed, @uncompressed, @name, @repo_order, @cat)", -1, &insert_statement, nullptr) != SQLITE_OK) || (sqlite3_prepare_v2(job_data->db, "INSERT OR REPLACE INTO pkglist (full_name, ver, arch, ext, location, " "summary, desc, compressed, uncompressed, name, repo_order) " "VALUES (@full_name, @ver, @arch, @ext, @location, @summary, " "@desc, @compressed, @uncompressed, @name, @repo_order)", -1, &insert_default_statement, nullptr) != SQLITE_OK)) { goto out; } query = sqlite3_mprintf("UPDATE pkglist SET full_name = @full_name, ver = @ver, arch = @arch, " "ext = @ext, location = @location, summary = @summary, " "desc = @desc, compressed = @compressed, uncompressed = @uncompressed " "WHERE name LIKE @name AND repo_order = %u", this->order); if (sqlite3_prepare_v2(job_data->db, query, -1, &update_statement, nullptr) != SQLITE_OK) { goto out; } desc = ""; sqlite3_exec(job_data->db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr); while (std::getline(data_in, line)) { if (boost::starts_with(line, "PACKAGE NAME: ")) { filename = line.substr(15); if (this->is_blacklisted(filename.value())) { filename.reset(); } } else if (filename.has_value() && boost::starts_with(line, "PACKAGE LOCATION: ")) { location = line.substr(21); /* Exclude ./ at the path beginning */ } else if (filename.has_value() && boost::starts_with(line, "PACKAGE SIZE (compressed): ")) { /* Remove the unit (kilobytes) */ std::from_chars(line.c_str() + 28, line.c_str() + line.size() - 2, pkg_compressed); pkg_compressed *= 1024; } else if (filename.has_value() && boost::starts_with(line, "PACKAGE SIZE (uncompressed): ")) { /* Remove the unit (kilobytes) */ std::from_chars(line.c_str() + 30, line.c_str() + line.size() - 2, pkg_uncompressed); pkg_uncompressed *= 1024; } else if (filename.has_value() && boost::starts_with(line, "PACKAGE DESCRIPTION:")) { std::getline(data_in, line); auto summary_position = line.find_first_of('('); if (summary_position != std::string::npos) /* Else summary = nullptr */ { summary = line.substr(summary_position + 1, line.size() - summary_position - 2); /* Without ( ) */ } pkg_tokens = split_package_name(filename.value()).value(); pkg_name_len = pkg_tokens[0].size(); /* Description begins with pkg_name: */ } else if (filename.has_value() && boost::starts_with(line, pkg_tokens[0])) { desc += line.substr(pkg_name_len + 1); } else if (filename.has_value() && line == "") { if (location != "patches/packages") /* Insert a new package */ { /* Get the package group based on its location */ auto category_position = location.find_last_of('/'); if (category_position != std::string::npos) { statement = insert_statement; sqlite3_bind_text(insert_statement, 12, location.c_str() + category_position + 1, -1, SQLITE_TRANSIENT); } else { statement = insert_default_statement; } sqlite3_bind_int(statement, 11, this->order); } else /* Update package information if it is a patch */ { statement = update_statement; } sqlite3_bind_text(statement, 1, pkg_tokens[3].c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, pkg_tokens[1].c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 3, pkg_tokens[2].c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 4, pkg_tokens[4].c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 5, location.c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 6, summary.has_value() ? summary.value().c_str() : nullptr, -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 7, desc.c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_int(statement, 8, pkg_compressed); sqlite3_bind_int(statement, 9, pkg_uncompressed); sqlite3_bind_text(statement, 10, pkg_tokens[0].c_str(), -1, SQLITE_TRANSIENT); sqlite3_step(statement); sqlite3_clear_bindings(statement); sqlite3_reset(statement); /* Reset for the next package */ summary.reset(); filename.reset(); location = ""; desc = ""; pkg_compressed = pkg_uncompressed = 0; } } sqlite3_exec(job_data->db, "END TRANSACTION", nullptr, nullptr, nullptr); /* Parse MANIFEST.bz2 */ for (const std::string& current_priority : this->priority) { manifest(job_data, tmpl, current_priority + "-MANIFEST.bz2"); } out: sqlite3_finalize(update_statement); sqlite3_free(query); sqlite3_finalize(insert_default_statement); sqlite3_finalize(insert_statement); } private: static const std::size_t max_buf_size = 8192; std::vector priority; /* * katja::Slackpkg::manifest: * @job: a #JobData. * @tmpl: temporary directory. * @filename: manifest filename * * Parse the manifest file and save the file list in the database. */ void manifest(JobData *job_data, const std::filesystem::path& tmpl, const std::string& filename) noexcept { FILE *manifest; int err, read_len; unsigned pos; char buf[max_buf_size]; std::optional rest; std::filesystem::path path; std::vector lines; BZFILE *manifest_bz2; std::regex pkg_expr, file_expr; std::smatch match_info; sqlite3_stmt *statement = nullptr; path = tmpl / this->name / filename; manifest = fopen(path.native().c_str(), "rb"); if (!manifest) { return; } if (!(manifest_bz2 = BZ2_bzReadOpen(&err, manifest, 0, 0, nullptr, 0))) { goto out; } /* Prepare regular expressions */ pkg_expr = std::regex("^\\|\\|[[:blank:]]+Package:[[:blank:]]+.+\\/(.+)\\.(t[blxg]z$)?", std::regex::optimize); file_expr = std::regex("^[-bcdlps][-r][-w][-xsS][-r][-w][-xsS][-r][-w]" "[-xtT][[:space:]][^[:space:]]+[[:space:]]+" "[[:digit:]]+[[:space:]][[:digit:]-]+[[:space:]]" "[[:digit:]:]+[[:space:]](?!install\\/|\\.)(.*)", std::regex::optimize); /* Prepare SQL statements */ if (sqlite3_prepare_v2(job_data->db, "INSERT INTO filelist (full_name, filename) VALUES (@full_name, @filename)", -1, &statement, nullptr) != SQLITE_OK) { goto out; } sqlite3_exec(job_data->db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr); while ((read_len = BZ2_bzRead(&err, manifest_bz2, buf, max_buf_size - 1))) { if ((err != BZ_OK) && (err != BZ_STREAM_END)) { break; } buf[read_len] = '\0'; /* Split the read text into lines */ boost::split(lines, buf, boost::is_any_of("\n")); if (rest.has_value()) { /* Add to the first line rest characters from the previous read operation */ lines[0] = rest.value() + lines[0]; rest.reset(); } if (err != BZ_STREAM_END) /* The last line can be incomplete */ { pos = lines.size() - 1; rest = lines[pos]; } for (const auto& line : lines) { std::optional full_name; if (std::regex_match(std::cbegin(line), std::cend(line), match_info, pkg_expr)) { if (match_info.size() > 2) { /* If the extension matches */ full_name = match_info.str(1); } else { full_name.reset(); } } if (full_name.has_value() && std::regex_match(std::cbegin(line), std::cend(line), match_info, file_expr)) { std::string pkg_filename = match_info.str(1); sqlite3_bind_text(statement, 1, full_name.value().c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, pkg_filename.c_str(), -1, SQLITE_TRANSIENT); sqlite3_step(statement); sqlite3_clear_bindings(statement); sqlite3_reset(statement); } } } sqlite3_exec(job_data->db, "END TRANSACTION", nullptr, nullptr, nullptr); BZ2_bzReadClose(&err, manifest_bz2); out: sqlite3_finalize(statement); fclose(manifest); } }; }