/* * 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 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; GFile *tmp_dir, *repo_tmp_dir; /* Create the temporary directory for the repository */ tmp_dir = g_file_new_for_path(tmpl.native().c_str()); repo_tmp_dir = g_file_get_child(tmp_dir, this->name.c_str()); g_file_make_directory(repo_tmp_dir, nullptr, nullptr); /* 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 = tmpl / this->name / "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: g_object_unref(repo_tmp_dir); g_object_unref(tmp_dir); 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, *filename = nullptr, *location = nullptr, *summary = nullptr, *line; unsigned pkg_compressed = 0, pkg_uncompressed = 0; std::uint8_t pkg_name_len; GString *desc; GFile *list_file; GFileInputStream *fin = nullptr; GDataInputStream *data_in = nullptr; sqlite3_stmt *insert_statement = nullptr, *update_statement = nullptr, *insert_default_statement = nullptr; sqlite3_stmt *statement; /* 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"; list_file = g_file_new_for_path(packages_txt.native().c_str()); fin = g_file_read(list_file, nullptr, nullptr); g_object_unref(list_file); if (!fin) { 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; } data_in = g_data_input_stream_new(G_INPUT_STREAM(fin)); desc = g_string_new(""); sqlite3_exec(job_data->db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr); while ((line = g_data_input_stream_read_line(data_in, nullptr, nullptr, nullptr))) { if (!strncmp(line, "PACKAGE NAME: ", 15)) { filename = g_strdup(line + 15); if (this->is_blacklisted(filename)) { g_free(filename); filename = nullptr; } } else if (filename && !strncmp(line, "PACKAGE LOCATION: ", 19)) { location = g_strdup(line + 21); /* Exclude ./ at the path beginning */ } else if (filename && !strncmp(line, "PACKAGE SIZE (compressed): ", 28)) { /* Remove the unit (kilobytes) */ pkg_compressed = atoi(g_strndup(line + 28, strlen(line + 28) - 2)) * 1024; } else if (filename && !strncmp(line, "PACKAGE SIZE (uncompressed): ", 30)) { /* Remove the unit (kilobytes) */ pkg_uncompressed = atoi(g_strndup(line + 30, strlen(line + 30) - 2)) * 1024; } else if (filename && !g_strcmp0(line, "PACKAGE DESCRIPTION:")) { g_free(line); line = g_data_input_stream_read_line(data_in, nullptr, nullptr, nullptr); /* Short description */ summary = g_strstr_len(line, -1, "("); if (summary) /* Else summary = nullptr */ { summary = g_strndup(summary + 1, strlen(summary) - 2); /* Without ( ) */ } pkg_tokens = split_package_name(filename).value(); pkg_name_len = pkg_tokens[0].size(); /* Description begins with pkg_name: */ } else if (filename && !strncmp(line, pkg_tokens[0].c_str(), pkg_name_len)) { g_string_append(desc, line + pkg_name_len + 1); } else if (filename && !g_strcmp0(line, "")) { if (g_strcmp0(location, "patches/packages")) /* Insert a new package */ { /* Get the package group based on its location */ const char *cat = g_strrstr(location, "/"); if (cat) { ++cat; statement = insert_statement; sqlite3_bind_text(insert_statement, 12, cat, -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, -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 6, summary, -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 7, desc->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 */ g_free(filename); g_free(location); g_free(summary); filename = location = summary = nullptr; g_string_assign(desc, ""); pkg_compressed = pkg_uncompressed = 0; } g_free(line); } sqlite3_exec(job_data->db, "END TRANSACTION", nullptr, nullptr, nullptr); g_string_free(desc, true); g_object_unref(data_in); /* 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); if (fin) { g_object_unref(fin); } } 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], *pkg_filename, *rest = nullptr, *start; std::filesystem::path path; char *full_name = nullptr; char **line, **lines; BZFILE *manifest_bz2; GRegex *pkg_expr = nullptr, *file_expr = nullptr; GMatchInfo *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 = g_regex_new("^\\|\\|[[:blank:]]+Package:[[:blank:]]+.+\\/(.+)\\.(t[blxg]z$)?", static_cast (G_REGEX_OPTIMIZE | G_REGEX_DUPNAMES), static_cast (0), nullptr); file_expr = g_regex_new("^[-bcdlps][-r][-w][-xsS][-r][-w][-xsS][-r][-w]" "[-xtT][[:space:]][^[:space:]]+[[:space:]]+" "[[:digit:]]+[[:space:]][[:digit:]-]+[[:space:]]" "[[:digit:]:]+[[:space:]](?!install\\/|\\.)(.*)", static_cast (G_REGEX_OPTIMIZE | G_REGEX_DUPNAMES), static_cast (0), nullptr); if (!(file_expr) || !(pkg_expr)) { goto out; } /* 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 */ lines = g_strsplit(buf, "\n", 0); if (rest) { /* Add to the first line rest characters from the previous read operation */ start = lines[0]; lines[0] = g_strconcat(rest, lines[0], nullptr); g_free(start); g_free(rest); } if (err != BZ_STREAM_END) /* The last line can be incomplete */ { pos = g_strv_length(lines) - 1; rest = lines[pos]; lines[pos] = nullptr; } for (line = lines; *line; line++) { if (g_regex_match(pkg_expr, *line, static_cast (0), &match_info)) { if (g_match_info_get_match_count(match_info) > 2) { /* If the extension matches */ g_free(full_name); full_name = g_match_info_fetch(match_info, 1); } else { full_name = nullptr; } } g_match_info_free(match_info); match_info = nullptr; if (full_name && g_regex_match(file_expr, *line, static_cast (0), &match_info)) { pkg_filename = g_match_info_fetch(match_info, 1); sqlite3_bind_text(statement, 1, full_name, -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, pkg_filename, -1, SQLITE_TRANSIENT); sqlite3_step(statement); sqlite3_clear_bindings(statement); sqlite3_reset(statement); g_free(pkg_filename); } g_match_info_free(match_info); } g_strfreev(lines); } sqlite3_exec(job_data->db, "END TRANSACTION", nullptr, nullptr, nullptr); g_free(full_name); BZ2_bzReadClose(&err, manifest_bz2); out: sqlite3_finalize(statement); if (file_expr) { g_regex_unref(file_expr); } if (pkg_expr) { g_regex_unref(pkg_expr); } fclose(manifest); } }; }