[PATCH] Check for manual interventions in arch-linux news site.
This patch adds a last-system update timelog to the database, checks the Arch Linux RSS feed for any possible manual interventions added since the last update time, and logs them so the user can keep them in mind. Signed-off-by: Salvador Pardiñas <darkfm@vera.com.uy> --- lib/libalpm/alpm.h | 6 ++ lib/libalpm/be_local.c | 82 +++++++++++++++ lib/libalpm/be_sync.c | 1 + lib/libalpm/db.h | 1 + lib/libalpm/dload.c | 12 ++- lib/libalpm/dload.h | 1 + lib/libalpm/meson.build | 1 + lib/libalpm/misc.c | 225 ++++++++++++++++++++++++++++++++++++++++ lib/libalpm/util.h | 2 + meson.build | 10 +- meson_options.txt | 3 + src/pacman/sync.c | 24 +++++ 12 files changed, 363 insertions(+), 5 deletions(-) create mode 100644 lib/libalpm/misc.c diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h index f372355c..e9f9067a 100644 --- a/lib/libalpm/alpm.h +++ b/lib/libalpm/alpm.h @@ -2901,6 +2901,12 @@ const char *alpm_version(void); * */ int alpm_capabilities(void); +/** Check if any manual intervention news item has been added since the last system upgrade. + * @param handle the context handle + * @return list of manual intervention news items or NULL if none + */ +alpm_list_t * alpm_check_manual_interventions(alpm_handle_t *handle); + /* End of libalpm_misc */ /** @} */ diff --git a/lib/libalpm/be_local.c b/lib/libalpm/be_local.c index e117b69d..57da6c57 100644 --- a/lib/libalpm/be_local.c +++ b/lib/libalpm/be_local.c @@ -409,6 +409,87 @@ static int is_dir(const char *path, struct dirent *entry) return 0; } +static int _local_db_set_last_sysupd(alpm_db_t UNUSED *db, const char *dbpath) +{ + char dbsysupdpath[PATH_MAX]; + FILE *dbsysupdfile; + + snprintf(dbsysupdpath, PATH_MAX, "%sALPM_LAST_SYSUPD", dbpath); + + dbsysupdfile = fopen(dbsysupdpath, "w"); + + if(dbsysupdfile == NULL) { + return 1; + } + + time_t now = time(NULL); + fprintf(dbsysupdfile, "%zu\n", now); + fclose(dbsysupdfile); + + return 0; +} + +static time_t _local_db_get_last_sysupd(alpm_db_t UNUSED *db, const char *dbpath) +{ + char dbsysupdpath[PATH_MAX]; + FILE *dbsysupdfile; + time_t now; + int t; + + snprintf(dbsysupdpath, PATH_MAX, "%sALPM_LAST_SYSUPD", dbpath); + + dbsysupdfile = fopen(dbsysupdpath, "r"); + + if(dbsysupdfile == NULL) { + return 0; + } + + t = fscanf(dbsysupdfile, "%li\n", &now); + fclose(dbsysupdfile); + + if(t != 1) { + return 0; + } + + return now; +} + +static int local_db_last_sysupd(alpm_db_t *db, time_t *output) +{ + struct dirent *ent = NULL; + const char *dbpath; + DIR *dbdir; + char dbverpath[PATH_MAX]; + FILE *dbverfile; + int t; + size_t version; + time_t tm; + + dbpath = _alpm_db_path(db); + if(dbpath == NULL) { + RET_ERR(db->handle, ALPM_ERR_DB_OPEN, -1); + } + + if(output != NULL) { + tm = _local_db_get_last_sysupd(db, dbpath); + + if(tm == 0) { + _local_db_set_last_sysupd(db, dbpath); + tm = _local_db_get_last_sysupd(db, dbpath); + } + + if(tm == 0) { + RET_ERR(db->handle, ALPM_ERR_DB_WRITE, -1); + } + + *output = tm; + } else { + _local_db_set_last_sysupd(db, dbpath); + } + + return 0; +} + static int local_db_add_version(alpm_db_t UNUSED *db, const char *dbpath) { char dbverpath[PATH_MAX]; @@ -1190,6 +1271,7 @@ static const struct db_operations local_db_ops = { .validate = local_db_validate, .populate = local_db_populate, .unregister = _alpm_db_unregister, + .last_sysupd = local_db_last_sysupd, }; alpm_db_t *_alpm_db_register_local(alpm_handle_t *handle) diff --git a/lib/libalpm/be_sync.c b/lib/libalpm/be_sync.c index 81676be9..95c111ba 100644 --- a/lib/libalpm/be_sync.c +++ b/lib/libalpm/be_sync.c @@ -700,6 +700,7 @@ struct db_operations sync_db_ops = { .validate = sync_db_validate, .populate = sync_db_populate, .unregister = _alpm_db_unregister, + .last_sysupd = NULL, }; alpm_db_t *_alpm_db_register_sync(alpm_handle_t *handle, const char *treename, diff --git a/lib/libalpm/db.h b/lib/libalpm/db.h index c9400365..36294020 100644 --- a/lib/libalpm/db.h +++ b/lib/libalpm/db.h @@ -59,6 +59,7 @@ struct db_operations { int (*validate) (alpm_db_t *); int (*populate) (alpm_db_t *); void (*unregister) (alpm_db_t *); + int (*last_sysupd) (alpm_db_t *, time_t *out_time); }; /* Database */ diff --git a/lib/libalpm/dload.c b/lib/libalpm/dload.c index 61a9e322..e624ee00 100644 --- a/lib/libalpm/dload.c +++ b/lib/libalpm/dload.c @@ -165,6 +165,10 @@ static int dload_progress_cb(void *file, curl_off_t dltotal, curl_off_t dlnow, off_t current_size, total_size; alpm_download_event_progress_t cb_data = {0}; + if(payload->hide_progress > 0) { + return 0; + } + /* avoid displaying progress bar for redirects with a body */ if(payload->respcode >= 300) { return 0; @@ -452,8 +456,8 @@ static int curl_retry_next_server(CURLM *curlm, CURL *curl, struct dload_payload fseek(payload->localf, 0, SEEK_SET); } - if(handle->dlcb) { - alpm_download_event_retry_t cb_data; + if(handle->dlcb && payload->hide_progress == 0) { + alpm_download_event_retry_t cb_data; cb_data.resume = payload->allow_resume; handle->dlcb(handle->dlcb_ctx, payload->remote_name, ALPM_DOWNLOAD_RETRY, &cb_data); } @@ -721,7 +725,7 @@ cleanup: unlink(payload->tempfile_name); } - if(handle->dlcb) { + if(handle->dlcb && payload->hide_progress == 0) { alpm_download_event_completed_t cb_data = {0}; cb_data.total = bytes_dl; cb_data.result = ret; @@ -838,7 +842,7 @@ static int curl_add_payload(alpm_handle_t *handle, CURLM *curlm, curl_easy_setopt(curl, CURLOPT_WRITEDATA, payload->localf); curl_multi_add_handle(curlm, curl); - if(handle->dlcb) { + if(handle->dlcb && payload->hide_progress == 0) { alpm_download_event_init_t cb_data = {.optional = payload->errors_ok}; handle->dlcb(handle->dlcb_ctx, payload->remote_name, ALPM_DOWNLOAD_INIT, &cb_data); } diff --git a/lib/libalpm/dload.h b/lib/libalpm/dload.h index 9438e04a..46f178a4 100644 --- a/lib/libalpm/dload.h +++ b/lib/libalpm/dload.h @@ -50,6 +50,7 @@ struct dload_payload { int trust_remote_name; int download_signature; /* specifies if an accompanion *.sig file need to be downloaded*/ int signature_optional; /* *.sig file is optional */ + int hide_progress; /* hide progress */ #ifdef HAVE_LIBCURL CURL *curl; char error_buffer[CURL_ERROR_SIZE]; diff --git a/lib/libalpm/meson.build b/lib/libalpm/meson.build index 607e91a3..0c801ad7 100644 --- a/lib/libalpm/meson.build +++ b/lib/libalpm/meson.build @@ -29,4 +29,5 @@ libalpm_sources = files(''' trans.h trans.c util.h util.c version.c + misc.c '''.split()) diff --git a/lib/libalpm/misc.c b/lib/libalpm/misc.c new file mode 100644 index 00000000..e278c41d --- /dev/null +++ b/lib/libalpm/misc.c @@ -0,0 +1,225 @@ +/* + * pkghash.c + * + * Copyright (c) 2023 Pacman Development Team <pacman-dev@lists.archlinux.org> + * + * 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 2 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> + +/* libalpm */ +#include "alpm_list.h" +#include "util.h" +#include "handle.h" +#include "alpm.h" +#include "dload.h" +#include "log.h" + +#ifdef HAVE_EXPAT +#include <expat.h> + +struct xmlread_t { + char* title; + char* date; + char* currtag; + enum _alpm_errno_t err; + int initem; + time_t sysupd_time; + alpm_list_t *interventions; +}; + +static XMLCALL void xml_start_element_handler(void *user_data, const char *name, const char UNUSED **attributes) +{ + struct xmlread_t *read = (struct xmlread_t *)user_data; + + if(read->err != ALPM_ERR_OK) { + return; + } + + if(strcmp(name, "item") == 0) { + FREE(read->title); + FREE(read->date); + FREE(read->currtag); + read->initem = 1; + } else { + STRDUP(read->currtag, name, (read->err = ALPM_ERR_MEMORY)); + } +} + +static XMLCALL void xml_end_element_handler(void *user_data, const char *name) +{ + struct xmlread_t *read = (struct xmlread_t *)user_data; + + if(read->err != ALPM_ERR_OK) { + return; + } + + if(strcmp(name, "item") == 0) { + read->initem = 0; + + if(read->date != NULL && read->title != NULL) { + if(strcasestr(read->title, "manual intervention") == NULL) { + return; + } + + const char *date_start = strchr(read->date, ',')+1; + const char *date; + struct tm tm_item = {0}; + STRNDUP(date, date_start, strlen("yyyy-mm-dd"), (read->err = ALPM_ERR_MEMORY); return); + char *strptime_result = strptime(date, "%Y-%m-%d", &tm_item); + if(strptime_result == NULL) { + read->err = ALPM_ERR_MEMORY; goto end; + } else { + time_t item_time = mktime(&tm_item); + if(item_time > read->sysupd_time) { + const char *title; + STRDUP(title, read->title, (read->err = ALPM_ERR_MEMORY); goto end); + + read->interventions = alpm_list_add(read->interventions, title); + } + } + end: + FREE(date); + } + } +} + +static XMLCALL void xml_character_data_handler(void *user_data, const char *value, int length) +{ + struct xmlread_t *read = (struct xmlread_t *)user_data; + + if(read->err != ALPM_ERR_OK) { + return; + } + + if(read->currtag == NULL || read->initem == 0) return; + if(strcmp(read->currtag, "title") == 0) { + CONCAT(read->title, value, length, (read->err = ALPM_ERR_MEMORY); return); + } else if(strcmp(read->currtag, "guid") == 0) { + CONCAT(read->date, value, length, (read->err = ALPM_ERR_MEMORY); return); + } +} + +alpm_list_t SYMEXPORT *alpm_check_manual_interventions(alpm_handle_t *handle) +{ + time_t tm; + /* check for new manual interventions */ + if(handle->db_local->ops->last_sysupd(handle->db_local, &tm) == 0) { + alpm_list_t *payloads = NULL; + struct dload_payload *payload = NULL; + const char *news_url = "https://archlinux.org/feeds/news/"; + const char *news_filepath = "/tmp/archlinux.news.xml"; + + CALLOC(payload, 1, sizeof(*payload), GOTO_ERR(handle, ALPM_ERR_MEMORY, skip)); + STRDUP(payload->fileurl, news_url, FREE(payload); GOTO_ERR(handle, ALPM_ERR_MEMORY, skip)); + STRDUP(payload->destfile_name, news_filepath, + FREE(payload->fileurl); FREE(payload); GOTO_ERR(handle, ALPM_ERR_MEMORY, skip)); + + payload->allow_resume = 0; + payload->random_partfile = 1; + + payload->handle = handle; + payload->trust_remote_name = 0; + payload->download_signature = 0; + payload->signature_optional = 1; + payload->force = 1; + payload->hide_progress = 1; + payloads = alpm_list_add(payloads, payload); + + if(_alpm_download(handle, payloads, "/tmp/") < 0) { + goto err; + } + + { + int done; + struct XML_ParserStruct *parser = XML_ParserCreate(NULL); + struct xmlread_t data = { + .title = NULL, + .date = NULL, + .err = ALPM_ERR_OK, + .currtag = NULL, + .initem = 0, + .interventions = NULL, + .sysupd_time = tm, + }; + if(!parser) { + GOTO_ERR(handle, ALPM_ERR_MEMORY, err); + } + + FILE *news_file = fopen(payload->destfile_name, "r"); + + XML_SetUserData(parser, &data); + XML_SetElementHandler(parser, xml_start_element_handler, xml_end_element_handler); + XML_SetCharacterDataHandler(parser, xml_character_data_handler); + + do { + void *const buf = XML_GetBuffer(parser, BUFSIZ); + if(!buf) { + GOTO_ERR(handle, ALPM_ERR_MEMORY, suberr); + } + + const size_t len = fread(buf, 1, BUFSIZ, news_file); + + if(ferror(news_file)) { + XML_ParserFree(parser); + GOTO_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, suberr); + } + + done = feof(news_file); + + if(XML_ParseBuffer(parser, (int)len, done) == XML_STATUS_ERROR) { + XML_ParserFree(parser); + fclose(news_file); + GOTO_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, suberr); + } + + if(data.err != ALPM_ERR_OK) { + fclose(news_file); + GOTO_ERR(handle, data.err, suberr); + } + } while (done == 0); + + XML_ParserFree(parser); + fclose(news_file); + + FREE(payload->fileurl); + FREE(payload->destfile_name); + FREE(payload); + FREE(data.title); + FREE(data.date); + FREE(data.currtag); + + handle->db_local->ops->last_sysupd(handle->db_local, NULL); /* set current system time as last system update */ + + return data.interventions; + suberr: + FREE(data.title); + FREE(data.date); + FREE(data.currtag); + } + + err: + FREE(payload->fileurl); + FREE(payload->destfile_name); + FREE(payload); + } + skip: + return NULL; +} +#else +alpm_list_t SYMEXPORT *alpm_check_manual_interventions(alpm_handle_t *handle) { + return NULL; +} +#endif diff --git a/lib/libalpm/util.h b/lib/libalpm/util.h index d6426e1b..00a0b35a 100644 --- a/lib/libalpm/util.h +++ b/lib/libalpm/util.h @@ -58,6 +58,8 @@ void _alpm_alloc_fail(size_t size); #define STRDUP(r, s, action) do { if(s != NULL) { r = strdup(s); if(r == NULL) { _alpm_alloc_fail(strlen(s)); action; } } else { r = NULL; } } while(0) #define STRNDUP(r, s, l, action) do { if(s != NULL) { r = strndup(s, l); if(r == NULL) { _alpm_alloc_fail(l); action; } } else { r = NULL; } } while(0) +#define CONCAT(r, s, l, action) do { if(r == NULL) { STRNDUP(r, s, l, action); } else {char *rnow = r; size_t newlen = strlen(rnow) + l + 1; MALLOC(r, newlen, action); strcpy(r, rnow); strncat(r, s, l); FREE(rnow);} } while (0) + #define FREE(p) do { free(p); p = NULL; } while(0) #define ASSERT(cond, action) do { if(!(cond)) { action; } } while(0) diff --git a/meson.build b/meson.build index 43705338..750b155f 100644 --- a/meson.build +++ b/meson.build @@ -127,6 +127,13 @@ else error('unhandled crypto value @0@'.format(want_crypto)) endif +needed_expat_version = '>=2.4.0' +expat = dependency('expat', + version : needed_expat_version, + required : get_option('expat'), + static : get_option('buildstatic')) +conf.set('HAVE_EXPAT', expat.found()) + foreach header : [ 'mntent.h', 'sys/mnttab.h', @@ -330,7 +337,7 @@ libcommon = static_library( gnu_symbol_visibility : 'hidden', install : false) -alpm_deps = [crypto_provider, libarchive, libcurl, libintl, gpgme] +alpm_deps = [crypto_provider, libarchive, libcurl, libintl, gpgme, expat] libalpm_a = static_library( 'alpm_objlib', @@ -487,6 +494,7 @@ message('\n '.join([ ' debug build : @0@'.format(get_option('buildtype') == 'debug'), ' Use libcurl : @0@'.format(conf.get('HAVE_LIBCURL')), ' Use GPGME : @0@'.format(conf.get('HAVE_LIBGPGME')), + ' Use expat : @0@'.format(conf.get('HAVE_EXPAT')), ' Use OpenSSL : @0@'.format(conf.has('HAVE_LIBSSL') and conf.get('HAVE_LIBSSL') == 1), ' Use nettle : @0@'.format(conf.has('HAVE_LIBNETTLE') and diff --git a/meson_options.txt b/meson_options.txt index d004002a..545616ef 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -54,6 +54,9 @@ option('gpgme', type : 'feature', value : 'auto', option('i18n', type : 'boolean', value : true, description : 'enable localization of pacman, libalpm and scripts') +option('expat', type : 'feature', value : 'auto', + description : 'use EXPAT for listing important news') + # tools option('file-seccomp', type: 'feature', value: 'auto', description: 'determine whether file is seccomp-enabled') diff --git a/src/pacman/sync.c b/src/pacman/sync.c index 4a01d402..1b9b36c2 100644 --- a/src/pacman/sync.c +++ b/src/pacman/sync.c @@ -689,6 +689,27 @@ cleanup: return ret; } +static void check_manual_interventions(void) +{ + alpm_list_t *manual_interventions = alpm_check_manual_interventions(config->handle); + alpm_list_t *manual_head = manual_interventions; + + if(manual_interventions != NULL) { + if(config->print == 0) { + printf(_("Some manual interventions may be required for this system upgrade:\n")); + while (manual_interventions != NULL) { + printf(" - %s\n", (char *) (manual_interventions->data)); + free(manual_interventions->data); + manual_interventions = alpm_list_next(manual_interventions); + } + } + } + + if(manual_head != NULL) { + alpm_list_free(manual_head); + } +} + static int sync_trans(alpm_list_t *targets) { int retval = 0; @@ -718,6 +739,9 @@ static int sync_trans(alpm_list_t *targets) alpm_logaction(config->handle, PACMAN_CALLER_PREFIX, "starting full system upgrade\n"); } + + check_manual_interventions(); + if(alpm_sync_sysupgrade(config->handle, config->op_s_upgrade >= 2) == -1) { pm_printf(ALPM_LOG_ERROR, "%s\n", alpm_strerror(alpm_errno(config->handle))); trans_release(); -- 2.42.1
On 5/11/23 13:51, Salvador Pardiñas wrote:
This patch adds a last-system update timelog to the database, checks the Arch Linux RSS feed for any possible manual interventions added since the last update time, and logs them so the user can keep them in mind.
pacman is not an Arch Linux specific tool, so this patch will not be accepted.
This patch adds a last-system update timelog to the database, checks the Arch Linux RSS feed for any possible manual interventions added since the last update time, and logs them so the user can keep them in mind.
pacman is not an Arch Linux specific tool, so this patch will not be accepted.
Would it be accepted if the source feed and matching text could be configured instead of being hardcoded? So that other distributions could point it at their equivalent source of important news.
Salvador Pardiñas writes:
This patch adds a last-system update timelog to the database, checks the Arch Linux RSS feed for any possible manual interventions added since the last update time, and logs them so the user can keep them in mind.
pacman is not an Arch Linux specific tool, so this patch will not be accepted.
Would it be accepted if the source feed and matching text could be configured instead of being hardcoded? So that other distributions could point it at their equivalent source of important news.
This feels like a lot of code for something which should probably be done through someone's RSS reader, web browser, or just through subscribing to arch-announce. Making it completely generic makes the code that has to be maintained even more burdensome. Reimplementing this existing functionality in pacman rather than using existing tools built for this purpose seems less than ideal. (I'd suggest sending an RFC in future so that the concept can be discussed first for new features, then you can get opinions on the concept before writing code.)
On 11/05/23 at 01:25pm, Salvador Pardiñas wrote:
This patch adds a last-system update timelog to the database, checks the Arch Linux RSS feed for any possible manual interventions added since the last update time, and logs them so the user can keep them in mind.
pacman is not an Arch Linux specific tool, so this patch will not be accepted.
Would it be accepted if the source feed and matching text could be configured instead of being hardcoded? So that other distributions could point it at their equivalent source of important news.
Just use a pacman hook, e.g. https://aur.archlinux.org/packages/informant
participants (4)
-
Allan McRae
-
Andrew Gregory
-
Chris Down
-
Salvador Pardiñas