Files
nvidia-installer/backup.c
Aaron Plattner 251ec51c59 545.23.06
2023-10-17 10:03:12 -07:00

1786 lines
49 KiB
C

/*
* nvidia-installer: A tool for installing NVIDIA software packages on
* Unix and Linux systems.
*
* Copyright (C) 2003 NVIDIA Corporation
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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>.
*
*
* backup.c - this source file contains functions used for backing up
* (and restoring) files that need to be moved out of the way during
* installation.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <ctype.h>
#include <stdlib.h>
#include "nvidia-installer.h"
#include "user-interface.h"
#include "backup.h"
#include "files.h"
#include "crc.h"
#include "misc.h"
#include "kernel.h"
#include "conflicting-kernel-modules.h"
#define BACKUP_DIRECTORY "/var/lib/nvidia"
#define BACKUP_LOG (BACKUP_DIRECTORY "/log")
#define BACKUP_MKDIR_LOG (BACKUP_DIRECTORY "/dirs")
/*
* Syntax for the backup log file:
*
* 1. The first line is the version string, assumed to be in the form:
* MAJOR.MINOR-PATCH
*
* 2. The second line is the driver description.
*
* XXX do we need anything to distinguish between official builds,
* nightlies, etc...?
*
* 3. The rest of the file is file entries; a file entry can be any one of:
*
* INSTALLED_FILE: <filename>
*
* INSTALLED_SYMLINK: <filename>
* <target>
*
* BACKED_UP_SYMLINK: <filename>
* <target>
* <permissions> <uid> <gid>
*
* BACKED_UP_FILE_NUM: <filename>
* <filesize> <permissions> <uid> <gid>
*
*/
#define BACKUP_LOG_PERMS (S_IRUSR|S_IWUSR)
#define BACKUP_DIRECTORY_PERMS (S_IRUSR|S_IWUSR|S_IXUSR)
/*
* uninstall struct
*
*/
typedef struct {
int num;
char *filename;
char *target;
uint32 crc;
mode_t mode;
uid_t uid;
gid_t gid;
int ok;
} BackupLogEntry;
typedef struct {
char *version;
char *description;
BackupLogEntry *e;
int n;
} BackupInfo;
static BackupInfo *read_backup_log_file(Options *op);
static void free_backup_info(BackupInfo *b);
static int check_backup_log_entries(Options *op, BackupInfo *b);
static int do_uninstall(Options *op, const char *version,
const int skip_depmod);
static int sanity_check_backup_log_entries(Options *op, BackupInfo *b);
static char *create_backwards_compatible_version_string(const char *str);
static int reverse_strlen_compare(const void *a, const void *b);
/*
* init_backup() - initialize the backup engine; this consists of
* creating a new backup directory, and writing to the log file that
* we're about to install a new driver version.
*/
int init_backup(Options *op, Package *p)
{
mode_t orig_mode;
char *version;
FILE *log;
/* remove the directory, if it already exists */
if (directory_exists(BACKUP_DIRECTORY)) {
if (!remove_directory(op, BACKUP_DIRECTORY)) {
return FALSE;
}
}
/* create the backup directory, with perms only for owner */
if (!mkdir_recursive(op, BACKUP_DIRECTORY, BACKUP_DIRECTORY_PERMS, FALSE)) {
return FALSE;
}
/*
* fopen below creates the file with mode 0666 & ~umask.
* In order to ensure the result of that calculation is BACKUP_LOG_PERMS,
* we temporarily set umask to ~BACKUP_LOG_PERMS to leave just the bits
* we want.
*
* This assumes that BACKUP_LOG does not already exist (if it does, the
* file permissions will not be modified.) This is assured by the
* directory removal and re-creation code above.
*/
orig_mode = umask(~BACKUP_LOG_PERMS);
/* create the log file */
log = fopen(BACKUP_LOG, "a");
umask(orig_mode);
if (!log) {
ui_error(op, "Unable to create backup log file '%s' (%s).",
BACKUP_LOG, strerror(errno));
return FALSE;
}
/* write the version and description */
version = create_backwards_compatible_version_string(p->version);
fprintf(log, "%s\n", version);
fprintf(log, "%s\n", p->description);
nvfree(version);
/* close the log file */
if (fclose(log) != 0) {
ui_error(op, "Error while closing backup log file '%s' (%s).",
BACKUP_LOG, strerror(errno));
return FALSE;
}
return TRUE;
} /* init_backup() */
/*
* do_backup() - backup the specified file. If it is a regular file,
* just move it into the backup directory, and add an entry to the log
* file.
*/
int do_backup(Options *op, const char *filename)
{
int len, ret, ret_val;
struct stat stat_buf;
char *tmp = NULL;
FILE *log;
uint32 crc;
static int backup_file_number = BACKED_UP_FILE_NUM;
ret_val = FALSE;
log = fopen(BACKUP_LOG, "a");
if (!log) {
ui_error(op, "Unable to open backup log file '%s' (%s).",
BACKUP_LOG, strerror(errno));
return FALSE;
}
if (lstat(filename, &stat_buf) == -1) {
switch (errno) {
case ENOENT:
ret_val = TRUE;
break;
default:
ui_error(op, "Unable to determine properties for file '%s' (%s).",
filename, strerror(errno));
}
goto done;
}
if (S_ISREG(stat_buf.st_mode)) {
crc = compute_crc(op, filename);
len = strlen(BACKUP_DIRECTORY) + 64;
tmp = nvalloc(len + 1);
snprintf(tmp, len, "%s/%d", BACKUP_DIRECTORY, backup_file_number);
if (!nvrename(op, filename, tmp)) {
ui_error(op, "Unable to backup file '%s'.", filename);
goto done;
}
fprintf(log, "%d: %s\n", backup_file_number, filename);
/* write the filesize, permissions, uid, gid */
fprintf(log, "%u %04o %d %d\n", crc, stat_buf.st_mode,
stat_buf.st_uid, stat_buf.st_gid);
backup_file_number++;
} else if (S_ISLNK(stat_buf.st_mode)) {
tmp = get_symlink_target(op, filename);
ret = unlink(filename);
if (ret == -1) {
ui_error(op, "Unable to remove symbolic link '%s' (%s).",
filename, strerror(errno));
goto done;
}
fprintf(log, "%d: %s\n", BACKED_UP_SYMLINK, filename);
fprintf(log, "%s\n", tmp);
fprintf(log, "%04o %d %d\n", stat_buf.st_mode,
stat_buf.st_uid, stat_buf.st_gid);
} else if (S_ISDIR(stat_buf.st_mode)) {
/* XXX IMPLEMENT ME: recursive moving of a directory */
ui_error(op, "Unable to backup directory '%s'.", filename);
goto done;
} else {
ui_error(op, "Unable to backup file '%s' (don't know how to deal with "
"file type).", filename);
goto done;
}
ret_val = TRUE;
done:
nvfree(tmp);
/* close the log file */
if (fclose(log) != 0) {
ui_error(op, "Error while closing backup log file '%s' (%s).",
BACKUP_LOG, strerror(errno));
ret_val = FALSE;
}
return ret_val;
} /* do_backup() */
int log_install_file(Options *op, const char *filename)
{
FILE *log;
uint32 crc;
/* open the log file */
log = fopen(BACKUP_LOG, "a");
if (!log) {
ui_error(op, "Unable to open backup log file '%s' (%s).",
BACKUP_LOG, strerror(errno));
return FALSE;
}
fprintf(log, "%d: %s\n", INSTALLED_FILE, filename);
crc = compute_crc(op, filename);
fprintf(log, "%u\n", crc);
/* close the log file */
if (fclose(log) != 0) {
ui_error(op, "Error while closing backup log file '%s' (%s).",
BACKUP_LOG, strerror(errno));
return FALSE;
}
return TRUE;
} /* log_install_file() */
int log_create_symlink(Options *op, const char *filename, const char *target)
{
FILE *log;
/* open the log file */
log = fopen(BACKUP_LOG, "a");
if (!log) {
ui_error(op, "Unable to open backup log file '%s' (%s).",
BACKUP_LOG, strerror(errno));
return FALSE;
}
fprintf(log, "%d: %s\n", INSTALLED_SYMLINK, filename);
fprintf(log, "%s\n", target);
/* close the log file */
if (fclose(log) != 0) {
ui_error(op, "Error while closing backup log file '%s' (%s).",
BACKUP_LOG, strerror(errno));
return FALSE;
}
return TRUE;
} /* log_create_symlink() */
static int parse_first_line(const char *buf, int *num, char **filename)
{
char *c, *local_buf;
if (!buf || !num || !filename) return FALSE;
local_buf = nvstrdup(buf);
c = local_buf;
while ((*c != '\0') && (*c != ':')) {
if (!isdigit(*c)) return FALSE;
c++;
}
if (*c == '\0') return FALSE;
*c = '\0';
*num = strtol(local_buf, NULL, 10);
c++;
while (isspace(*c)) c++;
*filename = nvstrdup(c);
free(local_buf);
return TRUE;
}
static int parse_mode_uid_gid(const char *buf, mode_t *mode,
uid_t *uid, gid_t *gid)
{
char *c, *local_buf, *str;
if (!buf || !mode || !uid || !gid) return FALSE;
local_buf = nvstrdup(buf);
c = str = local_buf;
while ((*c != '\0') && (isdigit(*c))) c++;
if (*c == '\0') return FALSE;
*c = '\0';
*mode = strtol(str, NULL, 8);
str = ++c;
while ((*c != '\0') && (isdigit(*c))) c++;
if (*c == '\0') return FALSE;
*c = '\0';
*uid = strtol(str, NULL, 10);
str = ++c;
while ((*c != '\0') && (isdigit(*c))) c++;
*c = '\0';
*gid = strtol(str, NULL, 10);
free(local_buf);
return TRUE;
}
static int parse_crc_mode_uid_gid(const char *buf, uint32 *crc, mode_t *mode,
uid_t *uid, gid_t *gid)
{
char *c, *local_buf, *str;
if (!buf || !crc || !mode || !uid || !gid) return FALSE;
local_buf = nvstrdup(buf);
c = str = local_buf;
while ((*c != '\0') && (isdigit(*c))) c++;
if (*c == '\0') return FALSE;
*c = '\0';
*crc = strtoul(str, NULL, 10);
str = ++c;
while ((*c != '\0') && (isdigit(*c))) c++;
if (*c == '\0') return FALSE;
*c = '\0';
*mode = strtol(str, NULL, 8);
str = ++c;
while ((*c != '\0') && (isdigit(*c))) c++;
if (*c == '\0') return FALSE;
*c = '\0';
*uid = strtol(str, NULL, 10);
str = ++c;
while ((*c != '\0') && (isdigit(*c))) c++;
*c = '\0';
*gid = strtol(str, NULL, 10);
free(local_buf);
return TRUE;
}
static int parse_crc(const char *buf, uint32 *crc)
{
char *c, *local_buf, *str;
if (!buf || !crc) return FALSE;
local_buf = nvstrdup(buf);
c = str = local_buf;
while ((*c != '\0') && (isdigit(*c))) c++;
*c = '\0';
*crc = strtoul(str, NULL, 10);
free(local_buf);
return TRUE;
} /* parse_crc() */
/*
* Syntax for the mkdir log file:
*
* Each line in the file contains the name of a directory that was created
* during driver installation.
*
* XXX pathnames containing '\n' will break both this file, and the regular
* backup log file.
*/
/*
* log_mkdir() - takes a newline-delimited list of directories and appends
* them to the log of directories created during installation.
*/
int log_mkdir(Options *op, const char *dirs)
{
FILE *log;
/*
* create the backup directory if it doesn't exist; BACKUP_MKDIR_LOG
* is within BACKUP_DIRECTORY, so the below fopen(3) call depends on
* the existence of BACKUP_DIRECTORY
*/
if (!directory_exists(BACKUP_DIRECTORY) &&
!mkdir_recursive(op, BACKUP_DIRECTORY, BACKUP_DIRECTORY_PERMS, FALSE)) {
return FALSE;
}
/* open the log file */
log = fopen(BACKUP_MKDIR_LOG, "a");
if (!log) {
ui_error(op, "Unable to open mkdir log file '%s' (%s).",
BACKUP_MKDIR_LOG, strerror(errno));
return FALSE;
}
fprintf(log, "%s", dirs);
/* close the log file */
if (fclose(log) != 0) {
ui_error(op, "Error while closing mkdir log file '%s' (%s).",
BACKUP_MKDIR_LOG, strerror(errno));
return FALSE;
}
return TRUE;
}
/*
* reverse_strlen_compare() - Compare two strings by length, for sorting
* in order of decreasing length.
*/
static int reverse_strlen_compare(const void *a, const void *b)
{
return strlen(*(char * const *)b) - strlen(*(char * const *)a);
}
/*
* rmdir_recursive() - Use BACKUP_MKDIR_LOG to find directories that were
* created by a previous nvidia-installer, and delete any such directories.
* Returns TRUE if the log is found and all directories are successfully
* deleted; returns FALSE if any directories failed to be deleted or the
* log isn't found. The log enries are processed in reverse order, so that
* child directories get properly deleted before their parents.
*/
static int rmdir_recursive(Options *op)
{
FILE *log;
char **dirs;
int eof = FALSE, ret = TRUE, lines, i;
/* open the log file */
log = fopen(BACKUP_MKDIR_LOG, "r");
if (!log) {
/* Fail silently: most likely, the current driver was simply installed
* with an nvidia-installer that didn't log created directories. */
return FALSE;
}
/* Count the number of lines */
for (lines = 0; !eof; lines++) {
char *dir;
dir = fget_next_line(log, &eof);
nvfree(dir);
}
rewind(log);
dirs = nvalloc(lines * sizeof(char*));
for (i = 0; i < lines; i++) {
dirs[i] = fget_next_line(log, &eof);
}
qsort(dirs, lines, sizeof(char*), reverse_strlen_compare);
for (i = 0; i < lines; i++) {
if (dirs[i]) {
/* Ignore empty lines and the backup directory itself, since it is
* never empty as long as the dirs file is still around. */
if (strlen(dirs[i]) && strcmp(dirs[i], BACKUP_DIRECTORY) != 0) {
if (rmdir(dirs[i]) != 0) {
ui_log(op, "Failed to delete the directory '%s' (%s).",
dirs[i], strerror(errno));
ret = FALSE;
}
}
}
nvfree(dirs[i]);
}
nvfree(dirs);
if (!ret) {
ui_warn(op, "Failed to delete some directories. See %s for details.",
op->log_file_name);
}
/* close the log file */
if (fclose(log) != 0) {
ui_error(op, "Error while closing mkdir log file '%s' (%s).",
BACKUP_MKDIR_LOG, strerror(errno));
return FALSE;
}
return ret;
}
/*
* do_uninstall() - this function uninstalls a previously installed
* driver, by parsing the BACKUP_LOG file.
*/
static int do_uninstall(Options *op, const char *version,
const int skip_depmod)
{
BackupLogEntry *e;
BackupInfo *b;
int i, len, ok;
char *tmpstr;
float percent;
int removal_failed = FALSE, restore_failed = FALSE;
static const char existing_installation_is_borked[] =
"Your driver installation has been "
"altered since it was initially installed; this may happen, "
"for example, if you have since installed the NVIDIA driver through "
"a mechanism other than nvidia-installer (such as your "
"distribution's native package management system). "
"nvidia-installer will attempt to uninstall as best it can.";
/* do we even have a backup directory? */
if (access(BACKUP_DIRECTORY, F_OK) == -1) {
ui_message(op, "No driver backed up.");
return FALSE;
}
if ((b = read_backup_log_file(op)) == NULL) return FALSE;
ok = check_backup_log_entries(op, b);
if (!ok) {
if (op->logging) {
ui_warn(op, "%s Please see the file '%s' for details.",
existing_installation_is_borked, op->log_file_name);
} else {
ui_warn(op, "%s", existing_installation_is_borked);
}
}
tmpstr = nvstrcat("Uninstalling ", b->description, " (",
b->version, "):", NULL);
run_distro_hook(op, "pre-uninstall");
ui_status_begin(op, tmpstr, "Uninstalling");
free(tmpstr);
/* Remove any installed DKMS modules */
if (dkms_module_installed(op, version, NULL)) {
ui_log(op, "DKMS module detected; removing...");
if (!dkms_remove_module(op, version)) {
ui_warn(op, "Failed to remove installed DKMS module!");
}
}
/*
* given the list of Backup logfile entries, perform the necessary
* operations:
*
* Step 1: remove everything that was previously installed
*
* Step 2: restore everything that was previously backed up
*/
for (i = 0; i < b->n; i++) {
percent = (float) i / (float) (b->n * 2);
e = &b->e[i];
if (!e->ok) continue;
switch (e->num) {
/*
* This is a file that was installed -- now delete it.
*/
case INSTALLED_FILE:
if (unlink(e->filename) == -1) {
ui_log(op, "Unable to remove installed file '%s' (%s).",
e->filename, strerror(errno));
removal_failed = TRUE;
}
ui_status_update(op, percent, "%s", e->filename);
break;
case INSTALLED_SYMLINK:
if (unlink(e->filename) == -1) {
ui_log(op, "Unable to remove installed symlink '%s' (%s).",
e->filename, strerror(errno));
removal_failed = TRUE;
}
ui_status_update(op, percent, "%s", e->filename);
break;
}
}
for (i = 0; i < b->n; i++) {
percent = (float) (i + b->n) / (float) (b->n * 2);
e = &b->e[i];
if (!e->ok) continue;
switch (e->num) {
case INSTALLED_FILE:
case INSTALLED_SYMLINK:
/* nothing to do */
break;
case BACKED_UP_SYMLINK:
if (symlink(e->target, e->filename) == -1) {
/*
* XXX only print this warning if
* check_backup_log_entries() didn't see any problems.
*/
if (ok) {
restore_failed = TRUE;
}
ui_log(op, "Unable to restore symbolic link "
"%s -> %s (%s).", e->filename, e->target,
strerror(errno));
} else {
/* XXX do we need to chmod the symlink? */
if (lchown(e->filename, e->uid, e->gid)) {
ui_log(op, "Unable to restore owner (%d) and group "
"(%d) for symbolic link '%s' (%s).",
e->uid, e->gid, e->filename, strerror(errno));
restore_failed = TRUE;
}
}
ui_status_update(op, percent, "%s", e->filename);
break;
default:
len = strlen(BACKUP_DIRECTORY) + 64;
tmpstr = nvalloc(len + 1);
snprintf(tmpstr, len, "%s/%d", BACKUP_DIRECTORY, e->num);
if (!nvrename(op, tmpstr, e->filename)) {
ui_log(op, "Unable to restore file '%s'.", e->filename);
restore_failed = TRUE;
} else {
if (chown(e->filename, e->uid, e->gid)) {
ui_log(op, "Unable to restore owner (%d) and group "
"(%d) for file '%s' (%s).",
e->uid, e->gid, e->filename, strerror(errno));
restore_failed = TRUE;
} else {
if (chmod(e->filename, e->mode) == -1) {
ui_log(op, "Unable to restore permissions %04o for "
"file '%s'.", e->mode, e->filename);
restore_failed = TRUE;
}
}
}
ui_status_update(op, percent, "%s", e->filename);
free(tmpstr);
break;
}
}
if (removal_failed) {
ui_warn(op, "Failed to remove some installed files/symlinks. See %s "
"for details", op->log_file_name);
}
if (restore_failed) {
ui_warn(op, "Failed to restore some backed up files/symlinks, and/or "
"their attributes. See %s for details", op->log_file_name);
}
if (!rmdir_recursive(op)) {
ui_log(op, "Unable to delete directories created by previous "
"installation.");
}
ui_status_end(op, "done.");
/* remove the backup directory */
if (!remove_directory(op, BACKUP_DIRECTORY)) {
/* XXX what to do if this fails?... nothing */
}
if (!op->skip_module_unload) {
/*
* attempt to unload the kernel module(s), but don't abort if this
* fails: the kernel may not have been configured with support for
* module unloading or the user might have unloaded it themselves or the
* module might not have existed at all.
*/
for (i = 0; i < num_conflicting_kernel_modules; i++) {
rmmod_kernel_module(op, conflicting_kernel_modules[i]);
}
}
if (op->uninstall) {
/* Update modules.dep and the ldconfig(8) cache to remove entries for
* any DSOs and kernel modules that we just uninstalled. */
int status = 0;
ui_log(op, "Running %sldconfig:", op->skip_depmod ? "" : "depmod and ");
if (!op->skip_depmod) {
status |= run_command(op, NULL, FALSE, NULL, FALSE,
op->utils[DEPMOD], " -a ", op->kernel_name, NULL);
}
status |= run_command(op, NULL, FALSE, NULL, FALSE,
op->utils[LDCONFIG], NULL);
if (status == 0) {
ui_log(op, "done.");
} else {
ui_log(op, "error!");
ui_warn(op, "An error occurred while running depmod or ldconfig "
"after uninstallation: your system may have stale state "
"involving recently uninstalled files.");
}
/*
* In case systemd units were just uninstalled, run `systemctl
* daemon-reload` to reflect the change.
*/
if (op->utils[SYSTEMCTL]) {
char *cmd = nvstrcat(op->utils[SYSTEMCTL], " daemon-reload", NULL);
ui_log(op, "Running `%s`:", cmd);
status = run_command(op, NULL, FALSE, NULL, FALSE, cmd, NULL);
nvfree(cmd);
if (status == 0) {
ui_log(op, "done.");
} else {
ui_log(op, "error!");
ui_warn(op, "An error occurred while reloading the systemd "
"daemon configuration.");
}
}
}
run_distro_hook(op, "post-uninstall");
free_backup_info(b);
return TRUE;
} /* do_uninstall() */
/*
* read_backup_log_file() -
*/
static BackupInfo *read_backup_log_file(Options *op)
{
struct stat stat_buf;
char *buf, *c, *line, *filename;
int fd, num, length, line_num = 0;
float percent;
BackupLogEntry *e;
BackupInfo *b = NULL;
/* check the permissions of the backup directory */
if (stat(BACKUP_DIRECTORY, &stat_buf) == -1) {
ui_error(op, "Unable to get properties of %s (%s).",
BACKUP_DIRECTORY, strerror(errno));
return NULL;
}
if ((stat_buf.st_mode & PERM_MASK) != BACKUP_DIRECTORY_PERMS) {
ui_error(op, "The directory permissions of %s have been changed since"
"the directory was created!", BACKUP_DIRECTORY);
return NULL;
}
if ((fd = open(BACKUP_LOG, O_RDONLY)) == -1) {
ui_error(op, "Failure opening %s (%s).", BACKUP_LOG, strerror(errno));
return NULL;
}
if (fstat(fd, &stat_buf) == -1) {
ui_error(op, "Failure getting file properties for %s (%s).",
BACKUP_LOG, strerror(errno));
goto pre_map_fail;
}
if ((stat_buf.st_mode & PERM_MASK) != BACKUP_LOG_PERMS) {
ui_error(op, "The file permissions of %s have been changed since "
"the file was written!", BACKUP_LOG);
goto pre_map_fail;
}
/* map the file */
length = stat_buf.st_size;
buf = mmap(0, length, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);
if (!buf) {
ui_error(op, "Unable to mmap file '%s' (%s).", BACKUP_LOG,
strerror(errno));
return NULL;
}
ui_status_begin(op, "Parsing log file:", "Parsing");
b = nvalloc(sizeof(BackupInfo));
b->version = get_next_line(buf, &c, buf, length);
if (!b->version || !c) goto parse_error;
percent = (float) (c - buf) / (float) stat_buf.st_size;
ui_status_update(op, percent, NULL);
b->description = get_next_line(c, &c, buf, length);
if (!b->description || !c) goto parse_error;
b->n = 0;
b->e = NULL;
line_num = 3;
while (1) {
percent = (float) (c - buf) / (float) stat_buf.st_size;
ui_status_update(op, percent, NULL);
/* read and parse the next line */
line = get_next_line(c, &c, buf, length);
if (!line) break;
if (!parse_first_line(line, &num, &filename)) goto parse_error;
line_num++;
free(line);
/* grow the BackupLogEntry array */
b->n++;
b->e = (BackupLogEntry *)
nvrealloc(b->e, sizeof(BackupLogEntry) * b->n);
memset(&b->e[b->n - 1], 0, sizeof(BackupLogEntry));
e = &b->e[b->n - 1];
e->num = num;
e->filename = filename;
e->ok = TRUE;
switch(e->num) {
case INSTALLED_FILE:
line = get_next_line(c, &c, buf, length);
if (line == NULL) goto parse_error;
line_num++;
if (!parse_crc(line, &e->crc)) goto parse_error;
free(line);
break;
case INSTALLED_SYMLINK:
line = get_next_line(c, &c, buf, length);
if (line == NULL) goto parse_error;
line_num++;
e->target = line;
break;
case BACKED_UP_SYMLINK:
line = get_next_line(c, &c, buf, length);
if (line == NULL) goto parse_error;
line_num++;
e->target = line;
line = get_next_line(c, &c, buf, length);
if (line == NULL) goto parse_error;
line_num++;
if (!parse_mode_uid_gid(line, &e->mode, &e->uid, &e->gid))
goto parse_error;
free(line);
break;
default:
if (num < BACKED_UP_FILE_NUM) goto parse_error;
line = get_next_line(c, &c, buf, length);
if (line == NULL) goto parse_error;
line_num++;
if (!parse_crc_mode_uid_gid(line, &e->crc, &e->mode,
&e->uid, &e->gid)) goto parse_error;
free(line);
break;
}
if (!c) break;
}
ui_status_end(op, "done.");
munmap(buf, stat_buf.st_size);
close(fd);
return b;
parse_error:
ui_status_end(op, "error.");
munmap(buf, stat_buf.st_size);
ui_error(op, "Error while parsing line %d of '%s'.", line_num, BACKUP_LOG);
nvfree(b);
pre_map_fail:
close(fd);
return NULL;
} /* read_backup_log_file() */
/*
* free_backup_info() - free the data associated with BackupInfo
*/
static void free_backup_info(BackupInfo *b)
{
int i;
if (!b) return;
nvfree(b->version);
nvfree(b->description);
for (i = 0; i < b->n; i++) {
nvfree(b->e[i].filename);
nvfree(b->e[i].target);
}
nvfree((char *) b->e);
nvfree((char *) b);
} /* free_backup_info() */
/*
* check_backup_log_entries() - for each backup log entry, perform
* some basic sanity checks. Set the 'ok' field to FALSE if a
* particular entry should not be uninstalled/restored.
*/
static int check_backup_log_entries(Options *op, BackupInfo *b)
{
BackupLogEntry *e;
uint32 crc;
char *tmpstr;
int i, j, len, ret = TRUE;
float percent;
ui_status_begin(op, "Validating previous installation:", "Validating");
for (i = 0; i < b->n; i++) {
percent = (float) i / (float) (b->n);
e = &b->e[i];
switch (e->num) {
case INSTALLED_FILE:
/* check if the file still matches its backup log entry */
e->ok = check_installed_file(op, e->filename, e->mode, e->crc,
ui_log);
ret = ret && e->ok;
ui_status_update(op, percent, "%s", e->filename);
break;
case INSTALLED_SYMLINK:
/*
* check if the symlink is still there, and has the same
* target
*/
if (access(e->filename, F_OK) == -1) {
ui_log(op, "Unable to access previously installed "
"symlink '%s' (%s).", e->filename, strerror(errno));
ret = e->ok = FALSE;
} else {
tmpstr = get_symlink_target(op, e->filename);
if (!tmpstr) {
ret = e->ok = FALSE;
} else {
if (strcmp(tmpstr, e->target) != 0) {
ui_log(op, "The previously installed symlink '%s' "
"has target '%s', but it was installed "
"with target '%s'. %s will not be "
"uninstalled.",
e->filename, tmpstr, e->target, e->filename);
ret = e->ok = FALSE;
/*
* if an installed symbolic link has a
* different target, then we don't remove it.
* This also means that we shouldn't attempt
* to restore a backed up symbolic link of the
* same name, or whose target matches this
* target.
*/
for (j = 0; j < b->n; j++) {
if ((b->e[j].num == BACKED_UP_SYMLINK) &&
(strcmp(b->e[j].filename, e->filename) == 0)) {
b->e[j].ok = FALSE;
}
}
}
free(tmpstr);
}
}
ui_status_update(op, percent, "%s", e->filename);
break;
case BACKED_UP_SYMLINK:
/* nothing to do */
break;
default:
/*
* this is a backed up file; check that the file is still
* present and has the same crc
*/
len = strlen(BACKUP_DIRECTORY) + 64;
tmpstr = nvalloc(len + 1);
snprintf(tmpstr, len, "%s/%d", BACKUP_DIRECTORY, e->num);
if (access(tmpstr, F_OK) == -1) {
ui_log(op, "Unable to access backed up file '%s' "
"(saved as '%s') (%s).",
e->filename, tmpstr, strerror(errno));
ret = e->ok = FALSE;
} else {
crc = compute_crc(op, tmpstr);
if (crc != e->crc) {
ui_log(op, "Backed up file '%s' (saved as '%s) has "
"different checksum (%" PRIu32 ") than when it was "
"backed up (%" PRIu32"). %s will not be restored.",
e->filename, tmpstr, crc, e->crc, e->filename);
ret = e->ok = FALSE;
}
}
ui_status_update(op, percent, "%s", tmpstr);
free(tmpstr);
break;
}
}
ui_status_end(op, "done.");
return (ret);
} /* check_backup_log_entries() */
/*
* get_installed_driver_version_and_descr() - determine the currently
* installed driver version and description. Returns the description
* string if a previous driver is installed. Returns NULL if no
* driver is currently installed.
*
* XXX for now, we'll just get the installed driver version by reading
* BACKUP_LOG. This is probably insufficient, though; how do we
* detect a driver that was installed prior to the new installer?
* Should we look for the installed files on the system and pull nvid
* from them?
*
* XXX we should probably check the file permissions of BACKUP_LOG.
*/
int get_installed_driver_version_and_descr(Options *op,
char **pVersion, char **pDescr)
{
struct stat stat_buf;
char *c, *version = NULL, *descr = NULL, *buf = NULL;
char *version_line = NULL;
int length, fd = -1;
int ret = FALSE;
if ((fd = open(BACKUP_LOG, O_RDONLY)) == -1) goto done;
if (fstat(fd, &stat_buf) == -1) goto done;
/* map the file */
length = stat_buf.st_size;
buf = mmap(0, length, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);
if (!buf) goto done;
version_line = get_next_line(buf, &c, buf, length);
if (!version_line) goto done;
version = extract_version_string(version_line);
if (!version) goto done;
descr = get_next_line(c, NULL, buf, length);
if (!descr) goto done;
*pVersion = strdup(version);
*pDescr = strdup(descr);
ret = TRUE;
done:
nvfree(version_line);
nvfree(version);
nvfree(descr);
if (buf) munmap(buf, stat_buf.st_size);
if (fd != -1) close(fd);
return ret;
} /* get_installed_driver_version_and_descr() */
/*
* check_for_existing_driver() - Get the existing driver description
* and version from BACKUP_LOG. If an existing driver is present, ask
* the user if they really want it to be uninstalled.
*
* Returns TRUE if it is OK to continue with the installation process.
* Returns FALSE if the user decided they didn't want to continue with
* installation.
*
* If we are only installing kernel modules, then there must be an
* existing driver installation, and the version of that installation
* must match the modules we're trying to install.
*/
int check_for_existing_driver(Options *op, Package *p)
{
char *descr = NULL;
char *version = NULL;
int ret = FALSE;
int localRet;
if (!check_for_existing_rpms(op)) goto done;
localRet = get_installed_driver_version_and_descr(op, &version, &descr);
if (op->kernel_modules_only) {
if (!localRet) {
ui_error(op, "No NVIDIA driver is currently installed; the "
"'--kernel-modules-only' option can only be used "
"to install the NVIDIA kernel modules on top of an "
"existing driver installation.");
goto done;
} else {
if (strcmp(p->version, version) != 0) {
ui_error(op, "The '--kernel-modules-only' option can only be "
"used to install kernel modules on top of an "
"existing driver installation of the same driver "
"version. The existing driver installation is "
"%s, but the kernel modules are %s.\n",
version, p->version);
goto done;
} else {
ret = TRUE;
goto done;
}
}
}
/* no existing driver -- it is fine to continue with installation */
if (!localRet) {
ret = TRUE;
goto done;
}
/*
* XXX we could do a comparison, here, to check that the Package
* version is greater than or equal to the installed version, and
* issue a warning if the user is downgrading. That doesn't seem
* necessary, though; I can't think of a good reason why
* downgrading is any different than upgrading.
*/
if (ui_multiple_choice(op, CONTINUE_ABORT_CHOICES,
NUM_CONTINUE_ABORT_CHOICES,
CONTINUE_CHOICE, /* Default choice */
"There appears to already be a driver installed on "
"your system (version: %s). As part of installing "
"this driver (version: %s), the existing driver will "
"be uninstalled. Are you sure you want to continue?",
version, p->version) == ABORT_CHOICE) {
ui_log(op, "Installation aborted.");
goto done;
}
ret = TRUE;
done:
nvfree(descr);
nvfree(version);
return ret;
} /* check_for_existing_driver() */
/*
* uninstall_existing_driver() - check if there is a driver already
* installed, and if there is, uninstall it.
*
* Currently, nothing about this function should cause installation to
* stop (so it always returns TRUE).
*/
int uninstall_existing_driver(Options *op, const int interactive,
const int skip_depmod)
{
int ret;
char *descr = NULL;
char *version = NULL;
ret = get_installed_driver_version_and_descr(op, &version, &descr);
if (!ret) {
if (interactive) {
ui_message(op, "There is no NVIDIA driver currently installed.");
}
return TRUE;
}
if (interactive && op->uninstall) {
const char *msg = "If you plan to no longer use the NVIDIA driver, you "
"should make sure that no X screens are configured to "
"use the NVIDIA X driver in your X configuration file. "
"If you used nvidia-xconfig to configure X, it may have "
"created a backup of your original configuration. Would "
"you like to run `nvidia-xconfig --restore-original-"
"backup` to attempt restoration of the original X "
"configuration file?";
run_nvidia_xconfig(op, TRUE, msg, FALSE);
}
ret = do_uninstall(op, version, skip_depmod);
if (ret) {
if (interactive) {
ui_message(op, "Uninstallation of existing driver: %s (%s) "
"is complete.", descr, version);
} else {
ui_log(op, "Uninstallation of existing driver: %s (%s) "
"is complete.", descr, version);
}
} else {
ui_error(op, "Uninstallation failed.");
}
nvfree(descr);
nvfree(version);
return TRUE;
} /* uninstall_existing_driver() */
/*
* Determine if the nvidia-uninstall executable at the path in 'uninstaller'
* supports the '--skip-depmod' option. To do this, we simply examine the help
* text for the presence of the option.
*/
static int check_skip_depmod_support(Options *op, const char *uninstaller)
{
/* exit status is 0 in case of success, so invert here */
return !run_command(op, NULL, FALSE, NULL, FALSE,
uninstaller, " -A | ", op->utils[GREP],
" -q '^ \\+--skip-depmod$'", NULL);
}
/*
* run_existing_uninstaller() - attempt to run `nvidia-uninstall` if it
* exists; if it does not exist or fails, fall back to normal uninstallation.
*/
int run_existing_uninstaller(Options *op)
{
char *uninstaller = find_system_util("nvidia-uninstall");
/*
* This function is run as part of installation. If we're about to install
* kernel modules and run depmod afterwards, we don't need to run depmod
* as part of uninstallation.
*/
int skip_depmod = !op->no_kernel_modules;
if (uninstaller) {
char *uninstall_log_dir, *uninstall_log_file, *uninstall_log_path;
char *data = NULL;
int ret;
skip_depmod = skip_depmod && check_skip_depmod_support(op, uninstaller);
/*
* Use DEFAULT_UNINSTALL_LOG_FILE_NAME as the name for the uninstall
* log file, in the same directory as op->log_file_name, be it the
* default location or a custom one.
*/
uninstall_log_dir = nv_dirname(op->log_file_name);
uninstall_log_file = nv_basename(DEFAULT_UNINSTALL_LOG_FILE_NAME);
uninstall_log_path = nvdircat(uninstall_log_dir, uninstall_log_file,
NULL);
nvfree(uninstall_log_dir);
nvfree(uninstall_log_file);
/* Run the uninstaller non-interactively, and explicitly log to the
* uninstall log location: older installers may not do so implicitly. */
ui_status_begin(op, "Uninstalling the previous installation", "");
ui_indeterminate_begin(op, "Running `%s...`", uninstaller);
ret = run_command(op, &data, FALSE, NULL, TRUE,
uninstaller, " -s --log-file-name=",
uninstall_log_path,
skip_depmod ? " --skip-depmod" : NULL,
NULL);
ui_indeterminate_end(op);
/* if nvidia-uninstall succeeded, return early; otherwise, fall back to
* uninstalling via the backup log file. */
if (ret != 0) {
ui_status_end(op, "failed.");
ui_log(op, "%s failed; see %s for more details.", uninstaller,
uninstall_log_path);
if (data && strlen(data)) {
ui_log(op, "The output from %s was:\n%s", uninstaller, data);
}
}
nvfree(data);
nvfree(uninstaller);
nvfree(uninstall_log_path);
if (ret == 0) {
ui_status_end(op, "done.");
return TRUE;
}
}
return uninstall_existing_driver(op, FALSE /* interactive */,
skip_depmod);
}
/*
* report_driver_information() - report basic information about the
* currently installed driver.
*/
int report_driver_information(Options *op)
{
char *descr, *version;
int ret;
ret = get_installed_driver_version_and_descr(op, &version, &descr);
if (!ret) {
ui_message(op, "There is no NVIDIA driver currently installed.");
return FALSE;
}
ui_message(op, "The currently installed driver is: '%s' "
"(version: %s).", descr, version);
nvfree(descr);
nvfree(version);
return TRUE;
} /* report_driver_information() */
/*
* test_installed_files() -
*/
int test_installed_files(Options *op)
{
BackupInfo *b;
int ret;
b = read_backup_log_file(op);
if (!b) return FALSE;
ret = sanity_check_backup_log_entries(op, b);
/* free resources associated with b */
free_backup_info(b);
return ret;
} /* test_installed_files() */
/*
* find_installed_file() - scan the backup log file for the specified
* filename; return TRUE if the filename is listed as an installed file.
*/
int find_installed_file(Options *op, char *filename)
{
BackupInfo *b;
BackupLogEntry *e;
int i, ret = FALSE;
if ((b = read_backup_log_file(op)) == NULL) return FALSE;
for (i = 0; i < b->n; i++) {
e = &b->e[i];
if ((e->num == INSTALLED_FILE) && strcmp(filename, e->filename) == 0) {
/* XXX should maybe compare inodes rather than
filenames? */
ret = TRUE;
break;
}
}
free_backup_info(b);
return ret;
} /* find_installed_file() */
/*
* sanity_check_backup_log_entries() - this function is very similar
* to check_backup_log_entries(); however, it varies in it's error
* messages because it is used as part of the sanity check path.
*/
static int sanity_check_backup_log_entries(Options *op, BackupInfo *b)
{
BackupLogEntry *e;
uint32 crc;
char *tmpstr;
int i, len, ret = TRUE;
float percent;
ui_status_begin(op, "Validating installation:", "Validating");
for (i = 0; i < b->n; i++) {
e = &b->e[i];
switch (e->num) {
case INSTALLED_FILE:
/* check if the file is still there, and has the same crc */
if (access(e->filename, F_OK) == -1) {
ui_error(op, "The installed file '%s' no longer exists.",
e->filename);
ret = FALSE;
} else {
crc = compute_crc(op, e->filename);
if (crc != e->crc) {
ui_error(op, "The installed file '%s' has a different "
"checksum (%" PRIu32 ") than when it was "
"installed (%" PRIu32 ").", e->filename, crc,
e->crc);
ret = FALSE;
}
}
break;
case INSTALLED_SYMLINK:
/*
* check if the symlink is still there, and has the same
* target
*/
if (access(e->filename, F_OK) == -1) {
ui_error(op, "The installed symbolic link '%s' no "
"longer exists.", e->filename);
ret = FALSE;
} else {
tmpstr = get_symlink_target(op, e->filename);
if (!tmpstr) {
ret = FALSE;
} else {
if (strcmp(tmpstr, e->target) != 0) {
ui_error(op, "The installed symbolic link '%s' "
"has target '%s', but it was installed "
"with target '%s'.",
e->filename, tmpstr, e->target);
ret = FALSE;
}
free(tmpstr);
}
}
break;
case BACKED_UP_SYMLINK:
/* nothing to do */
break;
default:
/*
* this is a backed up file; check that the file is still
* present and has the same crc
*/
len = strlen(BACKUP_DIRECTORY) + 64;
tmpstr = nvalloc(len + 1);
snprintf(tmpstr, len, "%s/%d", BACKUP_DIRECTORY, e->num);
if (access(tmpstr, F_OK) == -1) {
ui_error(op, "The backed up file '%s' (saved as '%s') "
"no longer exists.", e->filename, tmpstr);
ret = FALSE;
} else {
crc = compute_crc(op, tmpstr);
if (crc != e->crc) {
ui_error(op, "Backed up file '%s' (saved as '%s) has a "
"different checksum (%" PRIu32 ") than when it "
"was backed up (%" PRIu32 ").", e->filename,
tmpstr, crc, e->crc);
ret = FALSE;
}
}
free(tmpstr);
break;
}
percent = (float) i / (float) (b->n);
ui_status_update(op, percent, "%s", e->filename);
}
ui_status_end(op, "done.");
return ret;
} /* sanity_check_backup_log_entries */
/*
* create_backwards_compatible_version_string() - given the version
* string 'str', generate a version string to write to the backup log
* file that can be read by older nvidia-installers that assumed the
* X.Y-ZZZZ version format.
*
* Fortunately, old nvidia-installers' version parsing didn't care if
* there were extra digits beyond the four Z's (e.g., it could be
* X.Y-ZZZZZ, or X.Y-ZZZZZZZZ); they would just look for the first 4
* Z's if they needed to parse the version.
*
* So, the strategy is to take the new version string, remove any
* periods, and print that out as the old style version, followed by
* the real version string in parenthesis; e.g.,
*
* "1.0-105917 (105.9.17)"
*
* In this way, an old nvidia-installer will at least be able to parse
* the string, even though it may not understand it, but a new
* nvidia-installer can be smart and pull out the new version string.
*/
static char *create_backwards_compatible_version_string(const char *str)
{
char *version, *scratch, *s, *t;
int len;
/*
* create a scratch string by duping the passed in str, but
* collapse the scratch string by only retaining the digits; e.g.,
* "105.9.17" --> "105917"
*/
scratch = nvstrdup(str);
for (s = t = scratch; *t; t++) {
if (isdigit(*t)) {
*s++ = *t;
}
}
*s = '\0';
/* allocate a new string to contain the result */
len = strlen(str);
version = nvalloc((len * 2) + 16);
sprintf(version, "1.0-%s (%s)", scratch, str);
nvfree(scratch);
return version;
} /* create_backwards_compatible_version_string() */