mirror of
https://github.com/NVIDIA/nvidia-installer.git
synced 2025-07-23 02:13:00 +02:00
1786 lines
49 KiB
C
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() */
|