├── .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 |
--------------------------------------------------------------------------------