diff options
author | kx <kx@radix.pro> | 2023-04-11 01:18:34 +0300 |
---|---|---|
committer | kx <kx@radix.pro> | 2023-04-11 01:18:34 +0300 |
commit | 11c606a6888dc269ef018359469a7276c3ad8f67 (patch) | |
tree | 368294bb7cadcd5c44ccd082187d6a4433401027 /src/check-package.c | |
parent | 8c55752ed5b29a22fdab9faaa6ff27b7cafa6791 (diff) | |
download | pkgtools-11c606a6888dc269ef018359469a7276c3ad8f67.tar.xz |
Version 0.2.1pkgtools-0.2.1
Diffstat (limited to 'src/check-package.c')
-rw-r--r-- | src/check-package.c | 1511 |
1 files changed, 1511 insertions, 0 deletions
diff --git a/src/check-package.c b/src/check-package.c new file mode 100644 index 0000000..8997d64 --- /dev/null +++ b/src/check-package.c @@ -0,0 +1,1511 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <sys/file.h> /* flock(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <strings.h> /* index(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <sys/wait.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> +#include <cmpvers.h> + +#define PROGRAM_NAME "check-package" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *root = NULL, *pkgs_path = NULL, *pkg_fname = NULL, *pkg_found = NULL, + *tmpdir = NULL; + +int quiet = 0, print_broken_files = 0; + +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +static char *pkgname = NULL, + *pkgver = NULL, + *arch = NULL, + *distroname = NULL, + *distrover = NULL, + *group = NULL; + +static char *installed_version = NULL; + +enum _input_type { + IFMT_PKG = 0, + IFMT_LOG, + + IFMT_UNKNOWN +} input_format = IFMT_PKG; + + +#define FREE_PKGINFO_VARIABLES() \ + if( pkgname ) { free( pkgname ); } pkgname = NULL; \ + if( pkgver ) { free( pkgver ); } pkgver = NULL; \ + if( arch ) { free( arch ); } arch = NULL; \ + if( distroname ) { free( distroname ); } distroname = NULL; \ + if( distrover ) { free( distrover ); } distrover = NULL; \ + if( group ) { free( group ); } group = NULL; \ + if( installed_version ) { free( installed_version ); } installed_version = NULL + +void free_resources() +{ + if( root ) { free( root ); root = NULL; } + if( pkgs_path ) { free( pkgs_path ); pkgs_path = NULL; } + if( pkg_fname ) { free( pkg_fname ); pkg_fname = NULL; } + + if( selfdir ) { free( selfdir ); selfdir = NULL; } + + FREE_PKGINFO_VARIABLES(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <package|pkglog|pkgname>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "This utility checks if specified package is installed. If package\n" ); + fprintf( stdout, "or some another version of this package is already installed then\n" ); + fprintf( stdout, "this utility checks if installed package is correct.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " -p,--print-broken-files Print the list of broken directories,\n" ); + fprintf( stdout, " files, or symbolic links to stdout.\n" ); + fprintf( stdout, " -q,--quiet Do not display explanations for\n" ); + fprintf( stdout, " return codes.\n" ); + fprintf( stdout, " -r,--root=<DIR> Target rootfs path.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <package|pkglog|pkgname> The PKGNAME, PACKAGE tarball or PKGLOG.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Return codes:\n" ); + fprintf( stdout, " ------+---------------------------+--------------------\n" ); + fprintf( stdout, " code | status | what can be done\n" ); + fprintf( stdout, " ------+---------------------------+--------------------\n" ); + fprintf( stdout, " 30 | not installed | install\n" ); + fprintf( stdout, " 31 | installed correctly | nothing to do\n" ); + fprintf( stdout, " 32 | installed but not correct | repair, re-install\n" ); + fprintf( stdout, " 33 | installed correctly | upgrade\n" ); + fprintf( stdout, " 34 | installed but not correct | repair, upgrade\n" ); + fprintf( stdout, " 35 | installed correctly | downgrade\n" ); + fprintf( stdout, " 36 | installed but not correct | repair, downgrade\n" ); + fprintf( stdout, " ------+---------------------------+--------------------\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Other non-zero codes assumes that this utility faced to errors not\n" ); + fprintf( stdout, "related to quality of installed package.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "If package specified by short name (probably with version) instead\n" ); + fprintf( stdout, "of regular files such as package tarball or pkglog file then this\n" ); + fprintf( stdout, "utility doesn't check other installed versions of this package.\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + signal( SIGCHLD, SIG_DFL ); +} + + +static enum _input_type check_input_file( char *uncompress, const char *fname ) +{ + struct stat st; + size_t pkglog_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + if( uncompress ) + { + *uncompress = '\0'; + } + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + close( fd ); return IFMT_UNKNOWN; + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "PACKAGE", 7 ) ) + { + close( fd ); return IFMT_LOG; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + if( uncompress ) { *uncompress = 'x'; } + close( fd ); return IFMT_PKG; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + if( uncompress ) { *uncompress = 'j'; } + close( fd ); return IFMT_PKG; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + if( uncompress ) { *uncompress = 'J'; } + close( fd ); return IFMT_PKG; + } + + if( pkglog_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return IFMT_PKG; + } + } + + close( fd ); return IFMT_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ + const char* short_options = "hvpqr:"; + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "print-broken-files", no_argument, NULL, 'p' }, + { "quiet", no_argument, NULL, 'q' }, + { "root", required_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + case 'p': + { + print_broken_files = 1; + break; + } + case 'q': + { + quiet = 1; + break; + } + + case 'r': + { + if( optarg != NULL ) + { + root = xstrdup( (const char *)optarg ); + remove_trailing_slash( root ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + + if( optind < argc ) + { + pkg_fname = xstrdup( (const char *)argv[optind] ); + } + else + { + usage(); + } + + + if( !pkgs_path ) + { + struct stat st; + char *buf = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( !root ) + { + buf[0] = '/'; buf[1] = '\0'; + } + else + { + int len = strlen( root ); + + (void)strcpy( buf, (const char *)root ); + if( buf[ len - 1 ] != '/' ) + { + buf[len] = '/'; buf[len+1] = '\0'; + } + } + + (void)strcat( buf, PACKAGES_PATH ); + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file or directory: %s", buf, strerror( errno ) ); + } + + if( S_ISDIR(st.st_mode) ) + { + pkgs_path = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + { + FATAL_ERROR( "Defined --root '%s' is not a directory", buf ); + } + + } /* End if( !pkgs_path ) */ +} + + +/*********************************************************** + Remove leading spaces and take non-space characters only: + (Especialy for pkginfo lines) + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( xstrdup( (const char *)p ) ); +} + + +/******************************* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + + +/*************************************************************** + Probe functions: + */ +static void _probe_pkglog( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( pkg_found ) return; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + char *match = NULL; + char *pkglog = basename( path ); + + if( (match = strstr( pkglog, (const char *)basename( pkg_fname ) )) && match == pkglog ) + { + char *buf = NULL, *p = NULL, *q = NULL; + + p = q = buf = xstrdup( (const char *)pkglog ); + ++p; + while( *p != '\0' && !isblank(*p) && !(*q == '-' && isdigit(*p)) ) + { + /* package version starts with a number and separated by '-' */ + ++p; ++q; + } + *(--p) = '\0'; + + /******************************************************* + We have to make sure that the name we are looking for + is not shorter than the name of the found package. + */ + if( strlen(pkg_fname) >= strlen(buf) ) + { + + pkg_found = xstrdup( (const char *)path ); + free( buf ); + closedir( dir ); + return; + } + free( buf ); + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _probe_pkglog( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +/*********************************************************** + probe_package(): + --------------- + */ +char *probe_package( void ) +{ + char *ret = NULL; + + _probe_pkglog( (const char *)pkgs_path, NULL ); + if( pkg_found ) + { + free( pkg_fname ); + ret = pkg_fname = pkg_found; + } + + return ret; +} +/* + Enf of Probe functions. + ***********************************************************/ + + + +static void read_input_pkginfo( const char *pkginfo_fname ) +{ + char *ln = NULL; + char *line = NULL; + + FILE *pkginfo = NULL; + + if( pkginfo_fname != NULL ) + { + pkginfo = fopen( (const char *)pkginfo_fname, "r" ); + if( !pkginfo ) + { + FATAL_ERROR( "Cannot open %s file", pkginfo_fname ); + } + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkginfo )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "pkgname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgname = skip_spaces( p ); + } + if( (match = strstr( ln, "pkgver" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgver = skip_spaces( p ); + } + if( (match = strstr( ln, "arch" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) arch = skip_spaces( p ); + } + if( (match = strstr( ln, "distroname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) distroname = skip_spaces( p ); + } + if( (match = strstr( ln, "distrover" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) distrover = skip_spaces( p ); + } + + if( (match = strstr( ln, "group" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) group = skip_spaces( p ); + } + } + + free( line ); + + if( !pkgname || !pkgver || !arch || !distroname || !distrover ) + { + FATAL_ERROR( "Invalid input .PKGINFO file" ); + } + + fclose( pkginfo ); +} + +static void read_found_pkginfo( const char *pkginfo_fname ) +{ + char *ln = NULL; + char *line = NULL; + + FILE *pkginfo = NULL; + + char *pn = NULL, *pv = NULL, *ar = NULL, *dn = NULL, *dv = NULL; + + if( pkginfo_fname != NULL ) + { + pkginfo = fopen( (const char *)pkginfo_fname, "r" ); + if( !pkginfo ) + { + FATAL_ERROR( "Cannot open %s file", pkginfo_fname ); + } + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkginfo )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "pkgname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pn = skip_spaces( p ); + } + if( (match = strstr( ln, "pkgver" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pv = skip_spaces( p ); + } + if( (match = strstr( ln, "arch" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) ar = skip_spaces( p ); + } + if( (match = strstr( ln, "distroname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) dn = skip_spaces( p ); + } + if( (match = strstr( ln, "distrover" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) dv = skip_spaces( p ); + } + } + + free( line ); + + if( !pn || !pv || !ar || !dn || !dv ) + { + FATAL_ERROR( "Invalid input .PKGINFO file" ); + } + else + { + if( pn ) free( pn ); + if( pv ) installed_version = pv; + if( ar ) free( ar ); + if( dn ) free( dn ); + if( dv ) free( dv ); + } + + fclose( pkginfo ); +} + +static void _get_found_pkginfo( const char *pkglog ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)pkglog ) ); + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkginfo -d %s -o pkginfo,restore-links,filelist %s > /dev/null 2>&1", + selfdir, tmp, pkglog ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)pkglog ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)pkglog ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + read_found_pkginfo( (const char *)&tmp[0] ); + *(strstr( tmp, "/.PKGINFO" )) = '\0'; /* :restore tmpdir in tmp[] buffer */ + + free( tmp ); + free( cmd ); +} + +static void _search_pkglog( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + char *pname = (char *)dirpath + strlen( root ); /* do not remove leading '/' */ + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or group directory", pname ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or group is not a directory", pname ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", pname, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + char *match = NULL, *name = basename( path ); + + if( (match = strstr( name, pkgname )) && match == name ) + { + /**************************************************************** + Здесь мы еще должны проверить, что найденный пакет не имеет + более длинное имя, которое начинается с имени искомого пакета. + Полагаясь на факт, что версия может начинаться только с цифры, + мы пропускаем символ '-', разделяющий имя и версию пакета, + а затем проверяем начальный символ версии: + */ + if( *(name + strlen( pkgname )) == '-' && isdigit( *(name + strlen( pkgname ) + 1) ) ) + { + _get_found_pkginfo( (const char *)path ); + } + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + /************************************************************************** + NOTE: + In the Setup Database can be only one package with the same pkgname + but in different groups. For example, the package named 'cairo' + has two instance: libs/cairo-1.14.6 and xlibs/cairo-1.14.6. During + system installation the package libs/cairo-1.14.6 installed first + and then updated by xlibs/cairo-1.14.6 and PKGLOG of libs/cairo-1.14.6 + moved from /var/log/radix/packages to /var/log/radix/removed-packages. + + So here we have to look for the PKGLOG in all group directories: + */ + _search_pkglog( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +static void find_installed_package( void ) +{ + char *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", pkgs_path ); + + _search_pkglog( (const char *)&tmp[0], NULL ); + + free( tmp ); +} + +/*************************************************************** + check_input_package(): + --------------------- + + Возвращает: + -1 если пакет установлен, но его версия меньше + запрашиваемого, + 0 если пакет не установлен или версия установленного и + запрашиваемого равны, + 1 если пакет установлен, но его версия больше + запрашиваемого. + + В случае возврата -1 или 1, устанавливается переменная + installed_version, равная версии уже установленного пакета. + + В случае возврата нуля есть два варанта: + а) пакет установлен и его надо проверить на целостность + (переменная installed_version != NULL ); + б) пакет не установлен. + + Если installed_version != NULL, проверка целостности + пакета будет осуществлена, вне зависимости от его версии. + */ +static int check_input_package( void ) +{ + struct stat st; + char *fname = pkg_fname; + + enum _input_type type = IFMT_UNKNOWN; + char uncompress = '\0'; + + int ret = 0; + + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( (const char *)fname, &st ) == -1 ) + { + /************************************************* + Specified pkg_fname is not a file or directory. + Try to find installed package with name equal + to pkg_fname: + */ + fname = NULL; + fname = probe_package(); + if( !fname ) + { + if( !quiet ) fprintf( stdout, "Specified package '%s' is not installed.\n\n", pkg_fname ); + + exit_status = 30; /* Package is not installed: install */ + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); + } + } + + /* check pkg_fname again: */ + if( stat( (const char *)fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access input '%s' file: %s", fname, strerror( errno ) ); + } + + type = check_input_file( &uncompress, fname ); + if( type == IFMT_UNKNOWN ) + { + FATAL_ERROR( "Unknown format of input '%s' file", fname ); + } + + if( S_ISREG(st.st_mode) ) + { + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkginfo -d %s -o pkginfo,restore-links,filelist %s > /dev/null 2>&1", + selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + read_input_pkginfo( (const char *)&tmp[0] ); + *(strstr( tmp, "/.PKGINFO" )) = '\0'; /* :restore tmpdir in tmp[] buffer */ + + find_installed_package(); + + free( cmd ); + free( tmp ); + + if( installed_version ) + { + ret = cmp_version( (const char *)installed_version, (const char *)pkgver ); + } + } + else + { + FATAL_ERROR( "Input %s file is not a regular file", basename( (char *)fname ) ); + } + + return ret; +} + +static int check_package_integrity( void ) +{ + struct stat st; + FILE *fp = NULL; + + char *ln = NULL; + char *line = NULL; + + char *buf = NULL, *tmp = NULL; + + int restore_links = 0; + int ret = 1; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + /* Check if .RESTORELINKS is present */ + (void)sprintf( &tmp[0], "%s/.RESTORELINKS", tmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == 0) && (st.st_size > 8) ) + { + restore_links = 1; + } + + (void)sprintf( &tmp[0], "%s/.FILELIST", tmpdir ); + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .FILELIST file" ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + int dir = 0; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( *(ln + strlen(ln) - 1) == '/' ) { dir = 1; *(ln + strlen(ln) - 1) = '\0'; } + else { dir = 0; } + + if( !dir ) + { + char *p = rindex( ln, '.' ); + if( p && !strncmp( (const char *)p, ".new", 4 ) ) + { + /************************** + Do not check .new files: + */ + *p = '\0'; + } + } + + (void)sprintf( &buf[0], "%s/%s", root, ln ); + bzero( (void *)&st, sizeof( struct stat ) ); + + if( lstat( (const char *)&buf[0], &st ) == -1 ) + { + /* cannot access file list entry */ + if( dir ) + { + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s: no such directory\n", pkgname, installed_version, ln ); + } + else + { + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s: no such file\n", pkgname, installed_version, ln ); + } + + ret = 0; continue; + } + + if( dir ) + { + if( S_ISDIR(st.st_mode) == 0 ) + { + /* not a directory */ + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s: not a directory\n", pkgname, installed_version, ln ); + ret = 0; continue; + } + } + else + { + if( S_ISREG(st.st_mode) == 0 ) + { + /* not a regular file */ + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s: not a regular file\n", pkgname, installed_version, ln ); + ret = 0; continue; + } + if( !restore_links ) + { + if( S_ISLNK(st.st_mode) == 0 ) + { + /* not a symbolic link */ + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s: not a symbolic link\n", pkgname, installed_version, ln ); + ret = 0; continue; + } + } + } + } /* End of while( file list entry ) */ + fclose( fp ); + + + (void)sprintf( &tmp[0], "%s/.RESTORELINKS", tmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( (const char *)&tmp[0], &st ) == 0 ) + { + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .RESTORELINKS file" ); + } + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "; rm -rf " )) ) + { + char *q = NULL; + char *p = strstr( ln, "cd" ) + 2; + char *f = strstr( ln, "; rm -rf" ) + 8; + + if( !p || !f ) continue; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) ++p; + while( (*f == ' ' || *f == '\t') && *f != '\0' ) ++f; + + q = p; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + q = f; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + + if( p && f ) + { + (void)sprintf( &buf[0], "%s/%s/%s", root, p, f ); + bzero( (void *)&st, sizeof( struct stat ) ); + + if( lstat( (const char *)&buf[0], &st ) == -1 ) + { + /* cannot access restore links entry */ + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s/%s: no such file or directory\n", pkgname, installed_version, p, f ); + ret = 0; continue; + } + + if( S_ISLNK(st.st_mode) == 0 ) + { + /* not a symbolic link */ + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s/%s: not a symbolic link\n", pkgname, installed_version, p, f ); + ret = 0; continue; + } + } + } + } /* End of while( restore links entry ) */ + fclose( fp ); + } + + free( line ); + free( tmp ); + free( buf ); + + return ret; +} + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + tmpdir = _mk_tmpdir(); + if( !tmpdir ) + { + FATAL_ERROR( "Cannot create temporary directory" ); + } + + + { + int status = 0, correctly = 0; + + /********************************************************** + Fill pkginfo data and put or replace pkglog into tmpdir: + */ + status = check_input_package(); + + if( installed_version ) + { + /* In this case we have to check package integrity */ + correctly = check_package_integrity(); /* returns 1 if correct */ + } + + if( exit_status == EXIT_SUCCESS ) + { + if( status < 0 ) + { + if( !correctly ) + { + if( !quiet ) + fprintf( stdout, + "Previous version '%s' of specified package '%s-%s' is installed but not correct.\n\n", + installed_version, pkgname, pkgver ); + exit_status = 34; /* Package is installed but not correct: repair, upgrade */ + } + else + { + if( !quiet ) + fprintf( stdout, + "Previous version '%s' of specified package '%s-%s' is installed.\n\n", + installed_version, pkgname, pkgver ); + exit_status = 33; /* Package is installed correctly: upgrade */ + } + } + else if( status > 0 ) + { + if( !correctly ) + { + if( !quiet ) + fprintf( stdout, + "A newer version '%s' of specified package '%s-%s' is installed but not correct.\n\n", + installed_version, pkgname, pkgver ); + exit_status = 36; /* Package is installed but not correct: repair, downgrade */ + } + else + { + if( !quiet ) + fprintf( stdout, + "A newer version '%s' of specified package '%s-%s' is installed.\n\n", + installed_version, pkgname, pkgver ); + exit_status = 35; /* Package is installed correctly: downgrade */ + } + } + else + { + if( installed_version ) + { + if( !correctly ) + { + if( !quiet ) + fprintf( stdout, + "Specified package '%s-%s' is already installed but not correct.\n\n", + pkgname, pkgver ); + exit_status = 32; /* Package is installed but not correct: repair, re-install */ + } + else + { + if( !quiet ) + fprintf( stdout, + "Specified package '%s-%s' is already installed.\n\n", + pkgname, pkgver ); + exit_status = 31; /* Package is installed correctly: nothing to do */ + } + } + else + { + if( !quiet ) + fprintf( stdout, + "Specified package '%s-%s' is not installed.\n\n", + pkgname, pkgver ); + exit_status = 30; /* Package is not installed: install */ + } + } + } + + } + + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); +} |