1use anyhow::{Context, Result};
12use log::*;
13use std::{path::Path, process::Command};
14
15use crate::license_info::LicenseInfo;
16use crate::settings;
17use crate::{
18 file_info::FileInfo,
19 license_detector::{LicenseDetector, LicenseFile},
20};
21
22const RPM_EXECUTABLE: &str = "rpm";
23const RPM_QUERY_LICENSE: &str = "LICENSE";
24const RPM_QUERY_VERSION: &str = "VERSION";
25const RPM_QUERY_URL: &str = "URL";
26
27fn package_collect_files(package: &str, patterns: Vec<&str>) -> Result<Vec<String>> {
28 let mut found_files: Vec<_> = vec![];
29
30 let list_packages_args = vec!["-ql", package];
31
32 let output = Command::new(RPM_EXECUTABLE)
33 .args(list_packages_args.clone())
34 .output()
35 .context(format!(
36 "Cannot create command '{} {}'",
37 RPM_EXECUTABLE,
38 list_packages_args.join(" ")
39 ))?;
40 if !output.status.success() {
41 let error = String::from_utf8(output.stderr)?;
42 return Err(anyhow::anyhow!(
43 "Execution of '{} {}' failed",
44 RPM_EXECUTABLE,
45 list_packages_args.join(" ")
46 )
47 .context(error)
48 .context(format!("Cannot get package content of {package}")));
49 }
50 let stdout = String::from_utf8(output.stdout)?;
51 for file in stdout.lines() {
52 if patterns.iter().any(|p| file.contains(p)) {
53 let path = Path::new(file).to_path_buf();
54 if path.is_file() {
55 found_files.push(path.to_string_lossy().into_owned());
56 } else {
57 return Err(anyhow::anyhow!(
58 "The installed file {} of package {package} is missing in the file system",
59 path.to_string_lossy().into_owned(),
60 ));
61 }
62 }
63 }
64
65 Ok(found_files)
66}
67
68fn package_contains_file(package: &str, file: &str) -> Result<bool> {
69 let list_packages_args = vec!["-ql", package];
70
71 let output = Command::new(RPM_EXECUTABLE)
72 .args(list_packages_args.clone())
73 .output()
74 .context(format!(
75 "Cannot create command '{} {}'",
76 RPM_EXECUTABLE,
77 list_packages_args.join(" ")
78 ))?;
79 if !output.status.success() {
80 let error = String::from_utf8(output.stderr)?;
81 return Err(anyhow::anyhow!(
82 "Execution of '{} {}' failed",
83 RPM_EXECUTABLE,
84 list_packages_args.join(" ")
85 )
86 .context(error)
87 .context(format!("Cannot get package content of {package}")));
88 }
89 let stdout = String::from_utf8(output.stdout)?;
90 for line in stdout.lines() {
91 if line.contains(file) {
92 return Ok(true);
93 }
94 }
95
96 Ok(false)
97}
98
99pub fn package_name_of_lib(lib: &String) -> Result<String> {
100 let list_packages_args = vec!["--query", "--all", "--queryformat", "%{NAME}\\n"];
101
102 let output = Command::new(RPM_EXECUTABLE)
103 .args(list_packages_args.clone())
104 .output()
105 .context(format!(
106 "Cannot create command '{} {}'",
107 RPM_EXECUTABLE,
108 list_packages_args.join(" ")
109 ))?;
110 if !output.status.success() {
111 let error = String::from_utf8(output.stderr)?;
112 return Err(anyhow::anyhow!(
113 "Execution of '{} {}' failed",
114 RPM_EXECUTABLE,
115 list_packages_args.join(" ")
116 )
117 .context(error)
118 .context(format!("Cannot get package name for library {lib}")));
119 }
120 let mut packages: Vec<_> = vec![];
121 let stdout = String::from_utf8(output.stdout)?;
122 for package in stdout.lines() {
123 if package_contains_file(package, lib)? {
124 packages.push(package.to_string());
125 }
126 }
127 if packages.len() > 1 {
128 return Err(anyhow::anyhow!(
129 "Cannot find unique package containing the library '{lib}': {}",
130 packages.join(", ")
131 ));
132 }
133 if let Some(package) = packages.first() {
134 return Ok(package.clone());
135 }
136 Err(anyhow::anyhow!(
137 "Cannot find any package containing the library '{lib}'"
138 ))
139}
140
141fn query_package_info(package: &str, info: &str) -> Result<Option<String>> {
142 let query = format!("%{{{info}}}");
143 let list_packages_args = vec!["-q", package, "--queryformat", query.as_str()];
144
145 trace!(
146 "Query package info: {} {}",
147 RPM_EXECUTABLE,
148 list_packages_args.join(" ")
149 );
150 let output = Command::new(RPM_EXECUTABLE)
151 .args(list_packages_args.clone())
152 .output()
153 .context(format!(
154 "Cannot create command '{} {}'",
155 RPM_EXECUTABLE,
156 list_packages_args.join(" ")
157 ))?;
158 if !output.status.success() {
159 let error = String::from_utf8(output.stderr)?;
160 return Err(anyhow::anyhow!(
161 "Execution of '{} {}' failed",
162 RPM_EXECUTABLE,
163 list_packages_args.join(" ")
164 )
165 .context(error)
166 .context(format!("Cannot get query {info} for package {package}")));
167 }
168 let stdout = String::from_utf8(output.stdout)?;
169 if stdout.is_empty() {
170 return Ok(None);
171 }
172 Ok(Some(stdout))
173}
174
175pub fn package_info(package: &String, lib_info: &FileInfo, overrides: &[settings::Override]) -> Result<LicenseInfo> {
176 let override_info = settings::Override::find_override(package, overrides);
177
178 let license = if override_info.is_some_and(|x| x.license_id.is_some()) {
179 override_info.unwrap().license_id.clone()
180 } else {
181 query_package_info(package, RPM_QUERY_LICENSE)?
182 };
183 let version = query_package_info(package, RPM_QUERY_VERSION)?;
184 let url = query_package_info(package, RPM_QUERY_URL)?;
185
186 let license = if let Some(license) = license {
187 license
188 } else {
189 return Err(anyhow::anyhow!(
190 "Missing license identifier for RPM package {}",
191 package
192 ));
193 };
194
195 let license_file_patterns = vec!["COPY", "LICENSE", "License"];
196 let license_files: Vec<_> = if override_info.is_some_and(|x| !x.license_files.is_empty()) {
197 override_info
198 .unwrap()
199 .license_files
200 .iter()
201 .map(|license_file| LicenseFile {
202 id: license_file.id.clone(),
203 file: license_file.file.clone(),
204 })
205 .collect()
206 } else {
207 package_collect_files(package, license_file_patterns)?
208 .iter()
209 .map(|file| LicenseFile {
210 id: None,
211 file: file.clone(),
212 })
213 .collect()
214 };
215
216 let license_expression = if !license.is_empty() {
218 match spdx::Expression::parse_mode(license.as_str(), spdx::ParseMode::LAX)
219 .context(format!("Cannot parse license expression for package {package}"))
220 {
221 Ok(expr) => Some(expr),
222 Err(error) => {
223 warn!("{error:?}");
224 None
225 }
226 }
227 } else {
228 None
229 };
230
231 let license_ids = if let Some(expr) = &license_expression {
232 let mut collection = vec![];
233 expr.iter()
234 .map(|l| match l {
235 spdx::expression::ExprNode::Req(req) => Some(req),
236 spdx::expression::ExprNode::Op(_op) => None,
237 })
238 .for_each(|expr| {
239 if let Some(expr) = expr {
240 if let Some(license) = &expr.req.license.id() {
241 collection.push(*license);
242 }
243 }
244 });
245 collection
246 } else {
247 vec![]
248 };
249
250 let license_texts =
252 LicenseDetector::instance().detect_licenses(package, license_ids.as_slice(), license_files.as_slice())?;
253
254 Ok(LicenseInfo {
255 lib_info: lib_info.clone(),
256 package_name: package.clone(),
257 license,
258 license_expression,
259 license_texts,
260 version,
261 url,
262 })
263}