├── .gitignore ├── .ycm_extra_conf.py ├── README.pod ├── meson.build └── src ├── conf.c ├── conf.h ├── expac.c ├── expac.h └── util.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .*.swp 3 | expac 4 | expac.1 5 | .ycm_extra_conf.pyc 6 | -------------------------------------------------------------------------------- /.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Google Inc. 2 | # 3 | # This file is part of ycmd. 4 | # 5 | # ycmd is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ycmd is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ycmd. If not, see . 17 | 18 | import os 19 | import ycm_core 20 | 21 | # These are the compilation flags that will be used in case there's no 22 | # compilation database set (by default, one is not set). 23 | # CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. 24 | flags = [ 25 | '-Wall', 26 | '-Wextra', 27 | '-Werror', 28 | '-fexceptions', 29 | '-DNDEBUG', 30 | # THIS IS IMPORTANT! Without a "-std=" flag, clang won't know which 31 | # language to use when compiling headers. So it will guess. Badly. So C++ 32 | # headers will be compiled as C headers. You don't want that so ALWAYS specify 33 | # a "-std=". 34 | # For a C project, you would set this to something like 'c99' instead of 35 | # 'c++11'. 36 | '-std=c++17', 37 | # ...and the same thing goes for the magic -x option which specifies the 38 | # language that the files to be compiled are written in. This is mostly 39 | # relevant for c++ headers. 40 | # For a C project, you would set this to 'c' instead of 'c++'. 41 | '-x', 42 | 'c++', 43 | '-isystem', 44 | '/usr/include', 45 | '-isystem', 46 | '/usr/local/include', 47 | ] 48 | 49 | 50 | # Set this to the absolute path to the folder (NOT the file!) containing the 51 | # compile_commands.json file to use that instead of 'flags'. See here for 52 | # more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html 53 | # 54 | # Most projects will NOT need to set this to anything; you can just change the 55 | # 'flags' list of compilation flags. 56 | compilation_database_folder = 'build' 57 | 58 | if os.path.exists( compilation_database_folder ): 59 | database = ycm_core.CompilationDatabase( compilation_database_folder ) 60 | else: 61 | database = None 62 | 63 | SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] 64 | 65 | def DirectoryOfThisScript(): 66 | return os.path.dirname( os.path.abspath( __file__ ) ) 67 | 68 | 69 | def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): 70 | if not working_directory: 71 | return list( flags ) 72 | new_flags = [] 73 | make_next_absolute = False 74 | path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] 75 | for flag in flags: 76 | new_flag = flag 77 | 78 | if make_next_absolute: 79 | make_next_absolute = False 80 | if not flag.startswith( '/' ): 81 | new_flag = os.path.join( working_directory, flag ) 82 | 83 | for path_flag in path_flags: 84 | if flag == path_flag: 85 | make_next_absolute = True 86 | break 87 | 88 | if flag.startswith( path_flag ): 89 | path = flag[ len( path_flag ): ] 90 | new_flag = path_flag + os.path.join( working_directory, path ) 91 | break 92 | 93 | if new_flag: 94 | new_flags.append( new_flag ) 95 | return new_flags 96 | 97 | 98 | def IsHeaderFile( filename ): 99 | extension = os.path.splitext( filename )[ 1 ] 100 | return extension in [ '.h', '.hxx', '.hpp', '.hh' ] 101 | 102 | 103 | def GetCompilationInfoForFile( filename ): 104 | # The compilation_commands.json file generated by CMake does not have entries 105 | # for header files. So we do our best by asking the db for flags for a 106 | # corresponding source file, if any. If one exists, the flags for that file 107 | # should be good enough. 108 | if IsHeaderFile( filename ): 109 | basename = os.path.splitext( filename )[ 0 ] 110 | for extension in SOURCE_EXTENSIONS: 111 | replacement_file = basename + extension 112 | if os.path.exists( replacement_file ): 113 | compilation_info = database.GetCompilationInfoForFile( 114 | replacement_file ) 115 | if compilation_info.compiler_flags_: 116 | return compilation_info 117 | return None 118 | return database.GetCompilationInfoForFile( filename ) 119 | 120 | 121 | # This is the entry point; this function is called by ycmd to produce flags for 122 | # a file. 123 | def FlagsForFile( filename, **kwargs ): 124 | if database: 125 | # Bear in mind that compilation_info.compiler_flags_ does NOT return a 126 | # python list, but a "list-like" StringVec object 127 | compilation_info = GetCompilationInfoForFile( filename ) 128 | if not compilation_info: 129 | return None 130 | 131 | final_flags = MakeRelativePathsInFlagsAbsolute( 132 | compilation_info.compiler_flags_, 133 | compilation_info.compiler_working_dir_ ) 134 | else: 135 | relative_to = DirectoryOfThisScript() 136 | final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) 137 | 138 | return { 'flags': final_flags } 139 | -------------------------------------------------------------------------------- /README.pod: -------------------------------------------------------------------------------- 1 | =head1 NAME 2 | 3 | expac - alpm data extraction utility 4 | 5 | =head1 SYNOPSIS 6 | 7 | Usage: I [options] EformatE targets... 8 | 9 | =head1 DESCRIPTION 10 | 11 | expac is a data extraction tool for alpm databases. It features printf-like 12 | flexibility and aims to be used as a simple tool for other pacman based 13 | utilities which don't link against the library. It uses pacman.conf as a config 14 | file for locating and loading your local and sync databases. 15 | 16 | Invoking expac consists of supplying a format string, which is generally 17 | described by one to many of the formatting tokens (see the B 18 | section), any relevant options and zero to many targets. The format string 19 | B be the first non-option argument. Targets can be a simple package name, 20 | a query string (in the case of a search), or in repo/package syntax when the 21 | -sync option is supplied. 22 | 23 | =head1 OPTIONS 24 | 25 | =over 4 26 | 27 | =item B<-Q, --query> 28 | 29 | Search the local database for provided targets. This is the default behavior. 30 | 31 | =item B<-S, --sync> 32 | 33 | Search the sync databases for provided targets. 34 | 35 | =item B<-s, --search> 36 | 37 | Search for packages matching the strings specified by targets. This is a 38 | boolean AND query and regex is allowed. 39 | 40 | =item B<-g, --group> 41 | 42 | Return packages matching the specified targets as package groups. 43 | 44 | =item B<--config> 45 | 46 | Read from I for alpm initialization instead of I. 47 | 48 | =item B<-H, --humansize> 49 | 50 | Format package sizes in SI units according to I. Valid options are: 51 | 52 | B, K, M, G, T, P, E, Z, Y, R, Q 53 | 54 | You can also specify I, will determine a suitable unit for each result. 55 | 56 | =item B<-1, --readone> 57 | 58 | Stop searching after the first result. This only has an effect on -S operations 59 | without -s. 60 | 61 | =item B<-d, --delim> 62 | 63 | Separate each package with the specified I. The default value is a 64 | newline character. 65 | 66 | =item B<-l, --listdelim> 67 | 68 | Separate each list item with the specified I. Lists are any interpreted 69 | sequence specified with a capital letter. The default value is two spaces. 70 | 71 | =item B<-p, --file> 72 | 73 | Interpret targets as paths to local files. 74 | 75 | =item B<-t, --timefmt> 76 | 77 | Output time described by the specified I. This string is passed directly 78 | to strftime(3). The default format is %c. 79 | 80 | =item B<-v, --verbose> 81 | 82 | Output more. `Package not found' errors will be shown, and empty field values 83 | will display as 'None'. 84 | 85 | =item B<-V, --version> 86 | 87 | Display version information and quit. 88 | 89 | =item B<-h, --help> 90 | 91 | Display the help message and quit. 92 | 93 | =back 94 | 95 | =head1 FORMATTING 96 | 97 | The format argument allows the following interpreted sequences: 98 | 99 | %a architecture 100 | 101 | %B backup files 102 | 103 | %b build date 104 | 105 | %C conflicts with (no version strings) 106 | 107 | %D depends on 108 | 109 | %d description 110 | 111 | %E depends on (no version strings) 112 | 113 | %e package base 114 | 115 | %f filename (only with -S) 116 | 117 | %F files (only with -Q) 118 | 119 | %g base64 encoded PGP signature (only with -S) 120 | 121 | %G groups 122 | 123 | %H conflicts with 124 | 125 | %h sha256sum 126 | 127 | %i has install scriptlet (only with -Q) 128 | 129 | %J make depends on 130 | 131 | %K check depends on 132 | 133 | %k download size (only with -S) 134 | 135 | %l install date (only with -Q) 136 | 137 | %L licenses 138 | 139 | %m install size 140 | 141 | %M modified backup files (only with -Q) 142 | 143 | %n package name 144 | 145 | %N required by 146 | 147 | %O optional deps 148 | 149 | %o optional deps (no descriptions) 150 | 151 | %p packager name 152 | 153 | %P provides 154 | 155 | %R replaces (no version strings) 156 | 157 | %r repo 158 | 159 | %s md5sum 160 | 161 | %S provides (no version strings) 162 | 163 | %T replaces 164 | 165 | %u project URL 166 | 167 | %V package validation method 168 | 169 | %v version 170 | 171 | %W optional for 172 | 173 | %w install reason (only with -Q) 174 | 175 | %! result number (auto-incremented counter, starts at 0) 176 | 177 | %% literal % 178 | 179 | Note that for any lowercase tokens aside from %m and %k, full printf support is 180 | allowed, e.g. %-20n. This does not apply to any list based, date, or numerical 181 | output. 182 | 183 | Standard backslash escape sequences are supported, as per printf(1). 184 | 185 | =head1 EXAMPLES 186 | 187 | Emulate pacman's search function: 188 | 189 | =over 4 190 | 191 | $ expac -Ss '%r/%n %v\n %d' 192 | 193 | =back 194 | 195 | List the oldest 10 installed packages (by build date): 196 | 197 | =over 4 198 | 199 | $ expac --timefmt=%s '%b\t%n' | sort -n | head -10 200 | 201 | =back 202 | 203 | =head1 AUTHOR 204 | 205 | Dave Reisner Ed@falconindy.comE 206 | 207 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('expac', 'c', 2 | version : '10', 3 | license : 'MIT', 4 | default_options : [ 5 | 'c_std=c11', 6 | 'prefix=/usr', 7 | ]) 8 | 9 | libalpm = dependency('libalpm') 10 | 11 | conf = configuration_data() 12 | conf.set('_GNU_SOURCE', true) 13 | conf.set_quoted('PACKAGE_NAME', meson.project_name()) 14 | conf.set_quoted('PACKAGE_VERSION', meson.project_version()) 15 | 16 | cc = meson.get_compiler('c') 17 | 18 | # TODO: remove this once pacman 5.3 is commonplace 19 | have_three_arg_db_search = cc.compiles(''' 20 | #include 21 | int main(void) { return alpm_db_search(NULL, NULL, NULL); } 22 | ''', dependencies : [libalpm]) 23 | conf.set('HAVE_THREE_ARG_DB_SEARCH', have_three_arg_db_search) 24 | 25 | configure_file( 26 | output : 'config.h', 27 | configuration : conf) 28 | add_project_arguments('-include', 'config.h', language : 'c') 29 | 30 | executable( 31 | 'expac', 32 | files(''' 33 | src/expac.c 34 | src/conf.c src/conf.h 35 | src/util.h 36 | '''.split()), 37 | dependencies : [ 38 | libalpm, 39 | ], 40 | install : true) 41 | 42 | pod2man = find_program('pod2man') 43 | man = custom_target( 44 | 'man', 45 | output : 'expac.1', 46 | input : 'README.pod', 47 | command : [ 48 | pod2man, 49 | '--section=1', 50 | '--center=expac Manual', 51 | '--name=EXPAC', 52 | '--release=expac @0@'.format(meson.project_version()), 53 | '@INPUT@', '@OUTPUT@' 54 | ], 55 | install : true, 56 | install_dir : join_paths(get_option('mandir'), 'man1')) 57 | -------------------------------------------------------------------------------- /src/conf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "conf.h" 8 | #include "util.h" 9 | 10 | static size_t strtrim(char *str) 11 | { 12 | char *left = str, *right; 13 | 14 | if(!str || *str == '\0') { 15 | return 0; 16 | } 17 | 18 | while(isspace((unsigned char)*left)) { 19 | left++; 20 | } 21 | if(left != str) { 22 | memmove(str, left, (strlen(left) + 1)); 23 | left = str; 24 | } 25 | 26 | if(*str == '\0') { 27 | return 0; 28 | } 29 | 30 | right = strchr(str, '\0') - 1; 31 | while(isspace((unsigned char)*right)) { 32 | right--; 33 | } 34 | *++right = '\0'; 35 | 36 | return right - left; 37 | } 38 | 39 | static int is_section(const char *s, int n) 40 | { 41 | return s[0] == '[' && s[n-1] == ']'; 42 | } 43 | 44 | static int config_add_repo(config_t *config, char *reponame) 45 | { 46 | /* first time setup */ 47 | if(config->repos == NULL) { 48 | config->repos = calloc(10, sizeof(char*)); 49 | if(config->repos == NULL) { 50 | return -ENOMEM; 51 | } 52 | 53 | config->size = 0; 54 | config->capacity = 10; 55 | } 56 | 57 | /* grow when needed */ 58 | if(config->size == config->capacity) { 59 | void *ptr; 60 | const size_t newcap = config->capacity * 2.5; 61 | 62 | ptr = realloc(config->repos, newcap * sizeof(char*)); 63 | if(ptr == NULL) { 64 | return -ENOMEM; 65 | } 66 | 67 | config->repos = ptr; 68 | config->capacity = newcap; 69 | } 70 | 71 | config->repos[config->size] = strdup(reponame); 72 | ++config->size; 73 | 74 | return 0; 75 | } 76 | 77 | static int parse_one_file(config_t *config, const char *filename, char **section); 78 | 79 | static int parse_include(config_t *config, const char *include, char **section) { 80 | glob_t globbuf; 81 | int r = 0; 82 | 83 | if(glob(include, GLOB_NOCHECK, NULL, &globbuf) != 0) { 84 | fprintf(stderr, "warning: globbing failed on '%s': out of memory\n", 85 | include); 86 | return -ENOMEM; 87 | } 88 | 89 | for(size_t i = 0; i < globbuf.gl_pathc; ++i) { 90 | r = parse_one_file(config, globbuf.gl_pathv[i], section); 91 | if(r < 0) { 92 | break; 93 | } 94 | } 95 | 96 | globfree(&globbuf); 97 | return r; 98 | } 99 | 100 | static int parse_one_file(config_t *config, const char *filename, char **section) 101 | { 102 | _cleanup_(fclosep) FILE *fp = NULL; 103 | _cleanup_free_ char *line = NULL; 104 | size_t n = 0; 105 | int in_options; 106 | 107 | in_options = *section && strcmp(*section, "options") == 0; 108 | 109 | fp = fopen(filename, "r"); 110 | if(fp == NULL) { 111 | return -errno; 112 | } 113 | 114 | for(;;) { 115 | ssize_t len; 116 | char *val; 117 | 118 | errno = 0; 119 | len = getline(&line, &n, fp); 120 | if(len < 0) { 121 | if(errno != 0) { 122 | return -errno; 123 | } 124 | 125 | /* EOF */ 126 | break; 127 | } 128 | 129 | len = strtrim(line); 130 | if(len == 0 || line[0] == '#') { 131 | continue; 132 | } 133 | 134 | if(is_section(line, len)) { 135 | free(*section); 136 | *section = strndup(&line[1], len - 2); 137 | if(*section == NULL) { 138 | return -ENOMEM; 139 | } 140 | 141 | in_options = strcmp(*section, "options") == 0; 142 | if(!in_options) { 143 | int r; 144 | 145 | r = config_add_repo(config, *section); 146 | if(r < 0) { 147 | return r; 148 | } 149 | 150 | } 151 | continue; 152 | } 153 | 154 | val = line; 155 | strsep(&val, "="); 156 | strtrim(line); 157 | strtrim(val); 158 | 159 | if(strcmp(line, "Include") == 0) { 160 | int k; 161 | 162 | k = parse_include(config, val, section); 163 | if(k < 0) { 164 | return k; 165 | } 166 | } 167 | 168 | if(in_options) { 169 | if(strcmp(line, "DBPath") == 0) { 170 | config->dbpath = strdup(val); 171 | if(config->dbpath == NULL) { 172 | return -ENOMEM; 173 | } 174 | } else if(strcmp(line, "RootDir") == 0) { 175 | config->dbroot = strdup(val); 176 | if(config->dbroot == NULL) { 177 | return -ENOMEM; 178 | } 179 | } 180 | } 181 | } 182 | 183 | return 0; 184 | } 185 | 186 | void config_reset(config_t *config) 187 | { 188 | if(config == NULL) { 189 | return; 190 | } 191 | 192 | for(int i = 0; i < config->size; ++i) { 193 | free(config->repos[i]); 194 | } 195 | 196 | free(config->dbroot); 197 | free(config->dbpath); 198 | free(config->repos); 199 | } 200 | 201 | int config_parse(config_t *config, const char *filename) 202 | { 203 | _cleanup_free_ char *section = NULL; 204 | 205 | return parse_one_file(config, filename, §ion); 206 | } 207 | 208 | /* vim: set et ts=2 sw=2: */ 209 | -------------------------------------------------------------------------------- /src/conf.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONF_H 2 | #define _CONF_H 3 | 4 | typedef struct config_t { 5 | char **repos; 6 | int size; 7 | int capacity; 8 | 9 | char *dbroot; 10 | char *dbpath; 11 | } config_t; 12 | 13 | int config_parse(config_t *config, const char *filename); 14 | void config_reset(config_t *config); 15 | 16 | #endif /* _CONF_H */ 17 | 18 | /* vim: set et ts=2 sw=2: */ 19 | -------------------------------------------------------------------------------- /src/expac.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2014 Dave Reisner 2 | * 3 | * expac.c 4 | * 5 | * Permission is hereby granted, free of charge, to any person 6 | * obtaining a copy of this software and associated documentation 7 | * files (the "Software"), to deal in the Software without 8 | * restriction, including without limitation the rights to use, 9 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following 12 | * conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | * OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "expac.h" 40 | #include "conf.h" 41 | #include "util.h" 42 | 43 | #define DEFAULT_DELIM "\n" 44 | #define DEFAULT_LISTDELIM " " 45 | #define DEFAULT_TIMEFMT "%c" 46 | 47 | #ifndef PATH_MAX 48 | #define PATH_MAX 4096 49 | #endif 50 | 51 | static char const size_tokens[] = "BKMGTPEZYRQ"; 52 | static char const digits[] = "0123456789"; 53 | static char const printf_flags[] = "'-+ #0I"; 54 | 55 | bool opt_readone = false; 56 | bool opt_verbose = false; 57 | char opt_humansize = 'B'; 58 | package_corpus_t opt_corpus = CORPUS_LOCAL; 59 | search_what_t opt_what = SEARCH_EXACT; 60 | const char *opt_format = NULL; 61 | const char *opt_timefmt = DEFAULT_TIMEFMT; 62 | const char *opt_listdelim = DEFAULT_LISTDELIM; 63 | const char *opt_delim = DEFAULT_DELIM; 64 | const char *opt_config_file = "/etc/pacman.conf"; 65 | int opt_pkgcounter = 0; 66 | 67 | typedef const char *(*extractfn)(void*); 68 | 69 | static int is_valid_size_unit(char *u) 70 | { 71 | return u[0] != '\0' && u[1] == '\0' && 72 | memchr(size_tokens, *u, sizeof(size_tokens) - 1) != NULL; 73 | } 74 | 75 | static const char *alpm_backup_get_name(alpm_backup_t *bkup) 76 | { 77 | return bkup->name; 78 | } 79 | 80 | static double humanize_size(off_t bytes, const char target_unit, 81 | const char **label) 82 | { 83 | static const int unitcount = sizeof(size_tokens) - 1; 84 | static char label_buf[4]; 85 | 86 | double val = (double)bytes; 87 | int index; 88 | 89 | for(index = 0; index < unitcount; index++) { 90 | if(target_unit != '\0' && size_tokens[index] == target_unit) { 91 | break; 92 | } else if(target_unit == '\0' && val <= 2048.0 && val >= -2048.0) { 93 | break; 94 | } 95 | val /= 1024.0; 96 | } 97 | 98 | if(label) { 99 | sprintf(label_buf, "%c%s", size_tokens[index], 100 | size_tokens[index] == 'B' ? "" : "iB"); 101 | *label = label_buf; 102 | } 103 | 104 | return val; 105 | } 106 | 107 | static char *size_to_string(off_t pkgsize) 108 | { 109 | static char out[64]; 110 | 111 | if(opt_humansize == 'B') { 112 | snprintf(out, sizeof(out), "%jd", (intmax_t)pkgsize); 113 | } else { 114 | const char *unit = NULL; 115 | const double n = humanize_size(pkgsize, opt_humansize, &unit); 116 | snprintf(out, sizeof(out), "%.2f %s", n, unit); 117 | } 118 | 119 | return out; 120 | } 121 | 122 | static char *format_optdep(alpm_depend_t *optdep) 123 | { 124 | char *out = NULL; 125 | 126 | if(asprintf(&out, "%s: %s", optdep->name, optdep->desc) < 0) { 127 | return NULL; 128 | } 129 | 130 | return out; 131 | } 132 | 133 | static const char *alpm_dep_get_name(alpm_depend_t *dep) 134 | { 135 | return dep->name; 136 | } 137 | 138 | static void usage(void) 139 | { 140 | printf("expac %s\n" 141 | "Usage: expac [options] target...\n\n", PACKAGE_VERSION); 142 | printf( 143 | " Options:\n" 144 | " -Q, --query search local DB (default)\n" 145 | " -S, --sync search sync DBs\n" 146 | " -s, --search search for matching regex\n" 147 | " -g, --group return packages matching targets as groups\n" 148 | " -H, --humansize format package sizes in SI units, or \"auto\"\n" 149 | " -1, --readone return only the first result of a sync search\n\n" 150 | " -d, --delim separator used between packages (default: \"\\n\")\n" 151 | " -l, --listdelim separator used between list elements (default: \" \")\n" 152 | " -p, --file query local files instead of the DB\n" 153 | " -t, --timefmt date format passed to strftime (default: \"%%c\")\n" 154 | " --config read from for alpm initialization (default: /etc/pacman.conf)\n\n" 155 | " -v, --verbose be more verbose\n\n" 156 | " -V, --version display version information and exit\n" 157 | " -h, --help display this help and exit\n\n" 158 | "For more details see expac(1).\n"); 159 | } 160 | 161 | static void version(void) 162 | { 163 | printf("%s %s\n", program_invocation_short_name, PACKAGE_VERSION); 164 | } 165 | 166 | static int parse_options(int *argc, char **argv[]) 167 | { 168 | static struct option opts[] = { 169 | {"readone", no_argument, 0, '1'}, 170 | {"delim", required_argument, 0, 'd'}, 171 | {"listdelim", required_argument, 0, 'l'}, 172 | {"group", required_argument, 0, 'g'}, 173 | {"help", no_argument, 0, 'h'}, 174 | {"file", no_argument, 0, 'p'}, 175 | {"humansize", required_argument, 0, 'H'}, 176 | {"query", no_argument, 0, 'Q'}, 177 | {"sync", no_argument, 0, 'S'}, 178 | {"search", no_argument, 0, 's'}, 179 | {"timefmt", required_argument, 0, 't'}, 180 | {"verbose", no_argument, 0, 'v'}, 181 | {"version", no_argument, 0, 'V'}, 182 | {"config", required_argument, 0, 128}, 183 | {0, 0, 0, 0} 184 | }; 185 | 186 | for(;;) { 187 | int opt; 188 | 189 | opt = getopt_long(*argc, *argv, "1l:d:gH:hf:pQSst:Vv", opts, NULL); 190 | if(opt < 0) { 191 | break; 192 | } 193 | 194 | switch (opt) { 195 | case 'S': 196 | opt_corpus = CORPUS_SYNC; 197 | break; 198 | case 'Q': 199 | opt_corpus = CORPUS_LOCAL; 200 | break; 201 | case '1': 202 | opt_readone = true; 203 | break; 204 | case 'd': 205 | opt_delim = optarg; 206 | break; 207 | case 'g': 208 | opt_what = SEARCH_GROUPS; 209 | break; 210 | case 'l': 211 | opt_listdelim = optarg; 212 | break; 213 | case 'H': 214 | if(strcmp(optarg, "auto") == 0) { 215 | opt_humansize = '\0'; 216 | break; 217 | } 218 | if(!is_valid_size_unit(optarg)) { 219 | fprintf(stderr, "error: invalid SI size formatter: %s\n", optarg); 220 | return -1; 221 | } 222 | opt_humansize = *optarg; 223 | break; 224 | case 'h': 225 | usage(); 226 | exit(0); 227 | case 'p': 228 | opt_corpus = CORPUS_FILE; 229 | break; 230 | case 's': 231 | opt_what = SEARCH_REGEX; 232 | break; 233 | case 't': 234 | opt_timefmt = optarg; 235 | break; 236 | case 'V': 237 | version(); 238 | exit(0); 239 | break; 240 | case 'v': 241 | opt_verbose = true; 242 | break; 243 | case 128: 244 | opt_config_file = optarg; 245 | break; 246 | 247 | case '?': 248 | return -EINVAL; 249 | default: 250 | return -EINVAL; 251 | } 252 | } 253 | 254 | if(optind < *argc) { 255 | opt_format = (*argv)[optind++]; 256 | } else { 257 | fprintf(stderr, "error: missing format string (use -h for help)\n"); 258 | return -EINVAL; 259 | } 260 | 261 | *argc -= optind; 262 | *argv += optind; 263 | 264 | return 0; 265 | } 266 | 267 | static int print_escaped(const char *delim) 268 | { 269 | const char *f; 270 | int out = 0; 271 | 272 | for(f = delim; *f != '\0'; f++) { 273 | if(*f == '\\') { 274 | switch (*++f) { 275 | case '\\': 276 | fputc('\\', stdout); 277 | break; 278 | case '"': 279 | fputc('\"', stdout); 280 | break; 281 | case 'a': 282 | fputc('\a', stdout); 283 | break; 284 | case 'b': 285 | fputc('\b', stdout); 286 | break; 287 | case 'e': /* \e is nonstandard */ 288 | fputc('\033', stdout); 289 | break; 290 | case 'n': 291 | fputc('\n', stdout); 292 | break; 293 | case 'r': 294 | fputc('\r', stdout); 295 | break; 296 | case 't': 297 | fputc('\t', stdout); 298 | break; 299 | case 'v': 300 | fputc('\v', stdout); 301 | break; 302 | case '0': 303 | fputc('\0', stdout); 304 | break; 305 | default: 306 | fputc(*f, stdout); 307 | break; 308 | } 309 | ++out; 310 | } else { 311 | fputc(*f, stdout); 312 | ++out; 313 | } 314 | } 315 | 316 | return out; 317 | } 318 | 319 | static int print_list(alpm_list_t *list, extractfn fn) 320 | { 321 | alpm_list_t *i; 322 | int out = 0; 323 | 324 | if(!list) { 325 | if(opt_verbose) { 326 | out += printf("None"); 327 | } 328 | return out; 329 | } 330 | 331 | i = list; 332 | for(;;) { 333 | const char *item = fn ? fn(i->data) : i->data; 334 | if(item == NULL) { 335 | continue; 336 | } 337 | 338 | out += printf("%s", item); 339 | 340 | if((i = i->next)) { 341 | out += print_escaped(opt_listdelim); 342 | } else { 343 | break; 344 | } 345 | } 346 | 347 | return out; 348 | } 349 | 350 | static int print_allocated_list(alpm_list_t *list, extractfn fn) 351 | { 352 | int out = print_list(list, fn); 353 | alpm_list_free(list); 354 | return out; 355 | } 356 | 357 | static int print_time(time_t timestamp) { 358 | char buffer[64]; 359 | int out = 0; 360 | 361 | if(!timestamp) { 362 | if(opt_verbose) { 363 | out += printf("None"); 364 | } 365 | return out; 366 | } 367 | 368 | /* no overflow here, strftime prints a max of 64 including null */ 369 | strftime(&buffer[0], 64, opt_timefmt, localtime(×tamp)); 370 | out += printf("%s", buffer); 371 | 372 | return out; 373 | } 374 | 375 | static int print_filelist(alpm_filelist_t *filelist) 376 | { 377 | int out = 0; 378 | size_t i; 379 | 380 | for(i = 0; i < filelist->count; i++) { 381 | out += printf("%s", (filelist->files + i)->name); 382 | if(i < filelist->count - 1) { 383 | out += print_escaped(opt_listdelim); 384 | } 385 | } 386 | 387 | return out; 388 | } 389 | 390 | static bool backup_file_is_modified(const alpm_backup_t *backup_file) 391 | { 392 | char fullpath[PATH_MAX]; 393 | _cleanup_free_ char *md5sum = NULL; 394 | bool modified; 395 | 396 | /* TODO: respect expac->dbroot */ 397 | snprintf(fullpath, sizeof(fullpath), "/%s", backup_file->name); 398 | 399 | md5sum = alpm_compute_md5sum(fullpath); 400 | if(md5sum == NULL) { 401 | return false; 402 | } 403 | 404 | modified = strcmp(md5sum, backup_file->hash) != 0; 405 | 406 | return modified; 407 | } 408 | 409 | static alpm_list_t *get_modified_files(alpm_pkg_t *pkg) 410 | { 411 | alpm_list_t *i, *modified_files = NULL; 412 | 413 | for(i = alpm_pkg_get_backup(pkg); i; i = i->next) { 414 | const alpm_backup_t *backup = i->data; 415 | if(backup->hash && backup_file_is_modified(backup)) { 416 | modified_files = alpm_list_add(modified_files, backup->name); 417 | } 418 | } 419 | 420 | return modified_files; 421 | } 422 | 423 | static alpm_list_t *get_validation_method(alpm_pkg_t *pkg) 424 | { 425 | alpm_list_t *validation = NULL; 426 | 427 | alpm_pkgvalidation_t v = alpm_pkg_get_validation(pkg); 428 | 429 | if(v == ALPM_PKG_VALIDATION_UNKNOWN) { 430 | return alpm_list_add(validation, "Unknown"); 431 | } 432 | 433 | if(v & ALPM_PKG_VALIDATION_NONE) { 434 | return alpm_list_add(validation, "None"); 435 | } 436 | 437 | if(v & ALPM_PKG_VALIDATION_MD5SUM) { 438 | validation = alpm_list_add(validation, "MD5 Sum"); 439 | } 440 | if(v & ALPM_PKG_VALIDATION_SHA256SUM) { 441 | validation = alpm_list_add(validation, "SHA256 Sum"); 442 | } 443 | if(v & ALPM_PKG_VALIDATION_SIGNATURE) { 444 | validation = alpm_list_add(validation, "Signature"); 445 | } 446 | 447 | return validation; 448 | } 449 | 450 | static void print_pkg(alpm_pkg_t *pkg, const char *format) 451 | { 452 | const char *f, *end; 453 | int out = 0; 454 | 455 | end = format + strlen(format); 456 | 457 | for(f = format; f < end; f++) { 458 | if(*f == '%') { 459 | char fmt[64] = {0}; 460 | int l = 1; 461 | 462 | l += strspn(f + l, printf_flags); 463 | l += strspn(f + l, digits); 464 | memcpy(fmt, f, l); 465 | fmt[l] = 's'; 466 | 467 | f += l; 468 | switch (*f) { 469 | /* simple attributes */ 470 | case 'f': /* filename */ 471 | out += printf(fmt, alpm_pkg_get_filename(pkg)); 472 | break; 473 | case 'e': /* package base */ 474 | out += printf(fmt, alpm_pkg_get_base(pkg)); 475 | break; 476 | case 'n': /* package name */ 477 | out += printf(fmt, alpm_pkg_get_name(pkg)); 478 | break; 479 | case 'v': /* version */ 480 | out += printf(fmt, alpm_pkg_get_version(pkg)); 481 | break; 482 | case 'd': /* description */ 483 | out += printf(fmt, alpm_pkg_get_desc(pkg)); 484 | break; 485 | case 'u': /* project url */ 486 | out += printf(fmt, alpm_pkg_get_url(pkg)); 487 | break; 488 | case 'p': /* packager name */ 489 | out += printf(fmt, alpm_pkg_get_packager(pkg)); 490 | break; 491 | case 's': /* md5sum */ 492 | out += printf(fmt, alpm_pkg_get_md5sum(pkg)); 493 | break; 494 | case 'a': /* architecture */ 495 | out += printf(fmt, alpm_pkg_get_arch(pkg)); 496 | break; 497 | case 'i': /* has install scriptlet? */ 498 | out += printf(fmt, alpm_pkg_has_scriptlet(pkg) ? "yes" : "no"); 499 | break; 500 | case 'r': /* repo */ 501 | out += printf(fmt, alpm_db_get_name(alpm_pkg_get_db(pkg))); 502 | break; 503 | case 'w': /* install reason */ 504 | out += printf(fmt, alpm_pkg_get_reason(pkg) ? "dependency" : "explicit"); 505 | break; 506 | case '!': /* result number */ 507 | fmt[strlen(fmt)-1] = 'd'; 508 | out += printf(fmt, opt_pkgcounter++); 509 | break; 510 | case 'g': /* base64 gpg sig */ 511 | out += printf(fmt, alpm_pkg_get_base64_sig(pkg)); 512 | break; 513 | case 'h': /* sha256sum */ 514 | out += printf(fmt, alpm_pkg_get_sha256sum(pkg)); 515 | break; 516 | 517 | /* times */ 518 | case 'b': /* build date */ 519 | out += print_time(alpm_pkg_get_builddate(pkg)); 520 | break; 521 | case 'l': /* install date */ 522 | out += print_time(alpm_pkg_get_installdate(pkg)); 523 | break; 524 | 525 | /* sizes */ 526 | case 'k': /* download size */ 527 | out += printf(fmt, size_to_string(alpm_pkg_get_size(pkg))); 528 | break; 529 | case 'm': /* install size */ 530 | out += printf(fmt, size_to_string(alpm_pkg_get_isize(pkg))); 531 | break; 532 | 533 | /* lists */ 534 | case 'F': /* files */ 535 | out += print_filelist(alpm_pkg_get_files(pkg)); 536 | break; 537 | case 'N': /* requiredby */ 538 | out += print_list(alpm_pkg_compute_requiredby(pkg), NULL); 539 | break; 540 | case 'W': /* optionalfor */ 541 | out += print_list(alpm_pkg_compute_optionalfor(pkg), NULL); 542 | break; 543 | case 'L': /* licenses */ 544 | out += print_list(alpm_pkg_get_licenses(pkg), NULL); 545 | break; 546 | case 'G': /* groups */ 547 | out += print_list(alpm_pkg_get_groups(pkg), NULL); 548 | break; 549 | case 'E': /* depends (shortdeps) */ 550 | out += print_list(alpm_pkg_get_depends(pkg), (extractfn)alpm_dep_get_name); 551 | break; 552 | case 'J': /* makedepends */ 553 | out += print_list(alpm_pkg_get_makedepends(pkg), (extractfn)alpm_dep_compute_string); 554 | break; 555 | case 'K': /* checkdepends */ 556 | out += print_list(alpm_pkg_get_checkdepends(pkg), (extractfn)alpm_dep_compute_string); 557 | break; 558 | case 'D': /* depends */ 559 | out += print_list(alpm_pkg_get_depends(pkg), (extractfn)alpm_dep_compute_string); 560 | break; 561 | case 'O': /* optdepends */ 562 | out += print_list(alpm_pkg_get_optdepends(pkg), (extractfn)format_optdep); 563 | break; 564 | case 'o': /* optdepends (shortdeps) */ 565 | out += print_list(alpm_pkg_get_optdepends(pkg), (extractfn)alpm_dep_get_name); 566 | break; 567 | case 'H': /* conflicts */ 568 | out += print_list(alpm_pkg_get_conflicts(pkg), (extractfn)alpm_dep_compute_string); 569 | break; 570 | case 'C': /* conflicts (shortdeps) */ 571 | out += print_list(alpm_pkg_get_conflicts(pkg), (extractfn)alpm_dep_get_name); 572 | break; 573 | case 'S': /* provides (shortdeps) */ 574 | out += print_list(alpm_pkg_get_provides(pkg), (extractfn)alpm_dep_get_name); 575 | break; 576 | case 'P': /* provides */ 577 | out += print_list(alpm_pkg_get_provides(pkg), (extractfn)alpm_dep_compute_string); 578 | break; 579 | case 'R': /* replaces (shortdeps) */ 580 | out += print_list(alpm_pkg_get_replaces(pkg), (extractfn)alpm_dep_get_name); 581 | break; 582 | case 'T': /* replaces */ 583 | out += print_list(alpm_pkg_get_replaces(pkg), (extractfn)alpm_dep_compute_string); 584 | break; 585 | case 'B': /* backup */ 586 | out += print_list(alpm_pkg_get_backup(pkg), (extractfn)alpm_backup_get_name); 587 | break; 588 | case 'V': /* package validation */ 589 | out += print_allocated_list(get_validation_method(pkg), NULL); 590 | break; 591 | case 'M': /* modified */ 592 | out += print_allocated_list(get_modified_files(pkg), NULL); 593 | break; 594 | case '%': 595 | fputc('%', stdout); 596 | out++; 597 | break; 598 | default: 599 | fputc('?', stdout); 600 | out++; 601 | break; 602 | } 603 | } else if(*f == '\\') { 604 | char esc[3] = { f[0], f[1], '\0' }; 605 | out += print_escaped(esc); 606 | ++f; 607 | } else { 608 | fputc(*f, stdout); 609 | out++; 610 | } 611 | } 612 | 613 | /* only print a delimeter if any package data was outputted */ 614 | if(out > 0) { 615 | print_escaped(opt_delim); 616 | } 617 | } 618 | 619 | static alpm_list_t *all_packages(alpm_list_t *dbs) 620 | { 621 | alpm_list_t *i, *packages = NULL; 622 | 623 | for(i = dbs; i; i = i->next) { 624 | packages = alpm_list_join(packages, alpm_list_copy(alpm_db_get_pkgcache(i->data))); 625 | } 626 | 627 | return packages; 628 | } 629 | 630 | static alpm_list_t *search_packages(alpm_list_t *dbs, alpm_list_t *targets) 631 | { 632 | alpm_list_t *i, *packages = NULL; 633 | 634 | for(i = dbs; i; i = i->next) { 635 | alpm_list_t *results = NULL; 636 | #ifdef HAVE_THREE_ARG_DB_SEARCH 637 | alpm_db_search(i->data, targets, &results); 638 | #else 639 | results = alpm_db_search(i->data, targets); 640 | #endif 641 | packages = alpm_list_join(packages, results); 642 | } 643 | 644 | return packages; 645 | } 646 | 647 | static alpm_list_t *search_groups(alpm_list_t *dbs, alpm_list_t *groupnames) 648 | { 649 | alpm_list_t *i, *j, *packages = NULL; 650 | 651 | for(i = groupnames; i; i = i->next) { 652 | for(j = dbs; j; j = j->next) { 653 | alpm_group_t *grp = alpm_db_get_group(j->data, i->data); 654 | if(grp != NULL) { 655 | packages = alpm_list_join(packages, alpm_list_copy(grp->packages)); 656 | } 657 | } 658 | } 659 | 660 | return packages; 661 | } 662 | 663 | static alpm_list_t *search_exact(alpm_list_t *dblist, alpm_list_t *targets) 664 | { 665 | alpm_list_t *results = NULL; 666 | 667 | /* resolve each target individually from the repo pool */ 668 | for(alpm_list_t *t = targets; t; t = t->next) { 669 | char *pkgname, *reponame; 670 | alpm_list_t *r; 671 | int found = 0; 672 | 673 | pkgname = reponame = t->data; 674 | if(strchr(pkgname, '/')) { 675 | strsep(&pkgname, "/"); 676 | } else { 677 | reponame = NULL; 678 | } 679 | 680 | for(r = dblist; r; r = r->next) { 681 | alpm_db_t *repo = r->data; 682 | alpm_pkg_t *pkg; 683 | 684 | if(reponame && strcmp(reponame, alpm_db_get_name(repo)) != 0) { 685 | continue; 686 | } 687 | 688 | pkg = alpm_db_get_pkg(repo, pkgname); 689 | if(pkg == NULL) { 690 | continue; 691 | } 692 | 693 | found = 1; 694 | results = alpm_list_add(results, pkg); 695 | if(opt_readone) { 696 | break; 697 | } 698 | } 699 | 700 | if(!found && opt_verbose) { 701 | fprintf(stderr, "error: package `%s' not found\n", pkgname); 702 | } 703 | } 704 | 705 | return results; 706 | } 707 | 708 | static alpm_list_t *resolve_targets(alpm_list_t *dblist, alpm_list_t *targets) 709 | { 710 | if(targets == NULL) { 711 | return all_packages(dblist); 712 | } 713 | 714 | if(opt_what == SEARCH_REGEX) { 715 | return search_packages(dblist, targets); 716 | } 717 | 718 | if(opt_what == SEARCH_GROUPS) { 719 | return search_groups(dblist, targets); 720 | } 721 | 722 | return search_exact(dblist, targets); 723 | } 724 | 725 | static void expac_free(expac_t *expac) 726 | { 727 | if(expac == NULL) { 728 | return; 729 | } 730 | 731 | alpm_release(expac->alpm); 732 | free(expac); 733 | } 734 | 735 | static void expac_freep(expac_t **expac) { 736 | expac_free(*expac); 737 | } 738 | 739 | static int expac_new(expac_t **expac, const char *config_file) 740 | { 741 | expac_t *e; 742 | enum _alpm_errno_t alpm_errno = 0; 743 | config_t config; 744 | const char *dbroot = "/"; 745 | const char *dbpath = "/var/lib/pacman"; 746 | int r; 747 | 748 | e = calloc(1, sizeof(*e)); 749 | if(e == NULL) { 750 | return -ENOMEM; 751 | } 752 | 753 | memset(&config, 0, sizeof(config)); 754 | 755 | r = config_parse(&config, config_file); 756 | if(r < 0) { 757 | return r; 758 | } 759 | 760 | if(config.dbpath) { 761 | dbpath = config.dbpath; 762 | } 763 | 764 | if(config.dbroot) { 765 | dbroot = config.dbroot; 766 | } 767 | 768 | e->alpm = alpm_initialize(dbroot, dbpath, &alpm_errno); 769 | if(!e->alpm) { 770 | fprintf(stderr, "error: failed to initialize alpm: %s\n", alpm_strerror(alpm_errno)); 771 | return -alpm_errno; 772 | } 773 | 774 | for(int i = 0; i < config.size; ++i) { 775 | alpm_register_syncdb(e->alpm, config.repos[i], 0); 776 | } 777 | 778 | config_reset(&config); 779 | 780 | *expac = e; 781 | 782 | return 0; 783 | } 784 | 785 | static alpm_list_t *expac_search_files(expac_t *expac, alpm_list_t *targets) 786 | { 787 | alpm_list_t *i, *r = NULL; 788 | 789 | for(i = targets; i; i = i->next) { 790 | const char *path = i->data; 791 | alpm_pkg_t *pkg; 792 | 793 | if(alpm_pkg_load(expac->alpm, path, 0, 0, &pkg) != 0) { 794 | fprintf(stderr, "error: %s: %s\n", path, 795 | alpm_strerror(alpm_errno(expac->alpm))); 796 | continue; 797 | } 798 | 799 | r = alpm_list_add(r, pkg); 800 | } 801 | 802 | return r; 803 | } 804 | 805 | static alpm_list_t *expac_search_local(expac_t *expac, alpm_list_t *targets) 806 | { 807 | alpm_list_t *dblist, *r; 808 | 809 | dblist = alpm_list_add(NULL, alpm_get_localdb(expac->alpm)); 810 | r = resolve_targets(dblist, targets); 811 | alpm_list_free(dblist); 812 | 813 | return r; 814 | } 815 | 816 | static alpm_list_t *expac_search_sync(expac_t *expac, alpm_list_t *targets) 817 | { 818 | return resolve_targets(alpm_get_syncdbs(expac->alpm), targets); 819 | } 820 | 821 | static alpm_list_t *expac_search(expac_t *expac, package_corpus_t corpus, alpm_list_t *targets) 822 | { 823 | switch (corpus) { 824 | case CORPUS_LOCAL: 825 | return expac_search_local(expac, targets); 826 | case CORPUS_SYNC: 827 | return expac_search_sync(expac, targets); 828 | case CORPUS_FILE: 829 | return expac_search_files(expac, targets); 830 | } 831 | 832 | /* should be unreachable */ 833 | return NULL; 834 | } 835 | 836 | static int read_targets_from_file(FILE *in, alpm_list_t **targets) 837 | { 838 | char line[BUFSIZ]; 839 | int i = 0, end = 0, targets_added = 0; 840 | 841 | while(!end) { 842 | line[i] = fgetc(in); 843 | 844 | if(feof(in)) 845 | end = 1; 846 | 847 | if(isspace(line[i]) || end) { 848 | line[i] = '\0'; 849 | /* avoid adding zero length arg, if multiple spaces separate args */ 850 | if(i > 0) { 851 | if(!alpm_list_find_str(*targets, line)) { 852 | *targets = alpm_list_add(*targets, strdup(line)); 853 | } 854 | i = 0; 855 | ++targets_added; 856 | } 857 | } else { 858 | ++i; 859 | if(i >= BUFSIZ) { 860 | fprintf(stderr, "error: buffer overflow on stdin\n"); 861 | return -1; 862 | } 863 | } 864 | } 865 | 866 | return targets_added; 867 | } 868 | 869 | static int process_targets(int argc, char **argv, alpm_list_t **targets) 870 | { 871 | int allow_stdin; 872 | 873 | allow_stdin = !isatty(STDIN_FILENO); 874 | 875 | for(int i = 0; i < argc; ++i) { 876 | if(allow_stdin && strcmp(argv[i], "-") == 0) { 877 | int k; 878 | 879 | k = read_targets_from_file(stdin, targets); 880 | if(k < 0) { 881 | return k; 882 | } 883 | 884 | if(k == 0) { 885 | fputs("error: argument '-' specified with empty stdin\n", stderr); 886 | return -1; 887 | } 888 | 889 | allow_stdin = 0; 890 | } else { 891 | *targets = alpm_list_add(*targets, strdup(argv[i])); 892 | } 893 | } 894 | 895 | return 0; 896 | } 897 | 898 | int main(int argc, char *argv[]) 899 | { 900 | alpm_list_t *results = NULL, *targets = NULL; 901 | _cleanup_(expac_freep) expac_t *expac = NULL; 902 | int r; 903 | 904 | r = parse_options(&argc, &argv); 905 | if(r < 0) { 906 | return 1; 907 | } 908 | 909 | r = process_targets(argc, argv, &targets); 910 | if(r < 0) { 911 | return 1; 912 | } 913 | 914 | r = expac_new(&expac, opt_config_file); 915 | if(r < 0) { 916 | return 1; 917 | } 918 | 919 | results = expac_search(expac, opt_corpus, targets); 920 | if(results == NULL) { 921 | return 1; 922 | } 923 | 924 | for(alpm_list_t *i = results; i; i = i->next) { 925 | print_pkg(i->data, opt_format); 926 | } 927 | 928 | alpm_list_free_inner(targets, free); 929 | alpm_list_free(targets); 930 | alpm_list_free(results); 931 | 932 | return 0; 933 | } 934 | 935 | /* vim: set et ts=2 sw=2: */ 936 | -------------------------------------------------------------------------------- /src/expac.h: -------------------------------------------------------------------------------- 1 | #ifndef _EXPAC_H 2 | #define _EXPAC_H 3 | 4 | #include 5 | 6 | typedef enum package_corpus_t { 7 | CORPUS_LOCAL, 8 | CORPUS_SYNC, 9 | CORPUS_FILE, 10 | } package_corpus_t; 11 | 12 | typedef enum search_what_t { 13 | SEARCH_EXACT, 14 | SEARCH_GROUPS, 15 | SEARCH_REGEX, 16 | } search_what_t; 17 | 18 | typedef struct expac_t { 19 | alpm_handle_t *alpm; 20 | } expac_t; 21 | 22 | #endif /* _EXPAC_H */ 23 | 24 | /* vim: set et ts=2 sw=2: */ 25 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #ifndef _UTIL_H 2 | #define _UTIL_H 3 | 4 | #include 5 | #include 6 | 7 | static inline void freep(void *p) { free(*(void **)p); } 8 | static inline void fclosep(FILE **p) { if (*p) fclose(*p); } 9 | #define _cleanup_(x) __attribute__((cleanup(x))) 10 | #define _cleanup_free_ _cleanup_(freep) 11 | 12 | #endif /* _UTIL_H */ 13 | --------------------------------------------------------------------------------