Skip to content

Commit c85f1a4

Browse files
authored
Merge pull request #3 from itn3000/add-pe-file-ver
add file and product version get of PE(Portable Executable)
2 parents 611c62b + b30fb1b commit c85f1a4

File tree

6 files changed

+128
-4
lines changed

6 files changed

+128
-4
lines changed

Cargo.lock

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ anyhow = "1.0"
1313
clap = "3.0.0-beta.2"
1414
chrono = "0.4"
1515
serde_json = "1.0"
16-
serde = { version = "1.0", features = ["derive"] }
16+
serde = { version = "1.0", features = ["derive"] }
17+
pelite = "0.9.0"

Changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 0.3.0
2+
3+
* add file_version and product_version field
4+
15
# 0.2.1
26

37
* fix symlink with maxdepth(#1)

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ you can get help with `lsx --help`
2222
|file_type|file type("file","dir","link")|
2323
|last_modified|last updated time|
2424
|link_target|target path if the path is symbolic link, or null|
25+
|file_version|Windows file FileVersion if available|
26+
|product_version|Windows file ProductVersion if available|
2527

2628
## examples
2729

@@ -34,4 +36,6 @@ you can get help with `lsx --help`
3436
* output to file `x.csv`
3537
* `lsx -o x.csv tmp`
3638
* output file as ndjson
37-
* `lsx --output-format ndjson tmp`
39+
* `lsx --output-format ndjson tmp`
40+
* get file version
41+
* `lsx --get-version`

src/main.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use serde::Serialize;
44
use std::io::Write;
55
use std::rc::Rc;
66

7+
mod pe;
8+
79
#[derive(Debug, thiserror::Error)]
810
#[error("unknown format({name})")]
911
struct UnknownOutputFormat {
@@ -39,6 +41,8 @@ struct FindOption {
3941
dir_only: bool,
4042
#[clap(long, about = "output total size of directory(bytes)")]
4143
total_size: bool,
44+
#[clap(long, about = "get PE file version if available")]
45+
get_version: bool,
4246
}
4347

4448
enum RecordWriter<T>
@@ -72,6 +76,8 @@ where
7276
.as_ref()
7377
.unwrap_or(&String::new())
7478
.as_str(),
79+
record.file_version.unwrap_or(String::new()).as_str(),
80+
record.product_version.unwrap_or(String::new()).as_str(),
7581
])?;
7682
}
7783
Self::NdJson(v) => {
@@ -89,12 +95,15 @@ where
8995
pub fn output_header(&mut self) -> Result<()> {
9096
match self {
9197
Self::Csv(w) => {
98+
9299
w.write_record(&[
93100
"path",
94101
"link_target",
95102
"file_type",
96103
"length",
97104
"last_modified",
105+
"file_version",
106+
"product_version",
98107
])?;
99108
}
100109
Self::NdJson(_) => {},
@@ -111,6 +120,8 @@ struct FileRecord {
111120
file_type: String,
112121
last_modified: Option<String>,
113122
link_target: Option<String>,
123+
file_version: Option<String>,
124+
product_version: Option<String>,
114125
}
115126

116127
enum OutputStream {
@@ -145,6 +156,7 @@ struct FindContext<'a> {
145156
match_option: glob::MatchOptions,
146157
dir_only: bool,
147158
output_total: bool,
159+
get_version: bool,
148160
}
149161

150162
fn create_record_writer(
@@ -199,6 +211,7 @@ impl<'a> FindContext<'a> {
199211
match_option: match_option,
200212
output_total: output_total,
201213
dir_only: dir_only,
214+
get_version: opts.get_version,
202215
})
203216
}
204217
pub fn with_path(mut self, new_path: &std::path::Path) -> Self {
@@ -238,13 +251,18 @@ fn output_symlink_info<'a>(
238251
None
239252
};
240253
if !ctx.dir_only {
254+
let (file_version, product_version) = if ctx.get_version {
255+
pe::read_version_from_dll(path).unwrap_or((None, None))
256+
} else { (None, None) };
241257
write_record(
242258
&mut ctx.output_stream,
243259
path.to_string_lossy().as_ref(),
244260
Some(link_target),
245261
"link",
246262
Some(len),
247263
modified,
264+
file_version,
265+
product_version
248266
)?;
249267
}
250268
Ok((ctx, len))
@@ -276,12 +294,17 @@ fn output_symlink_file_info<'a>(
276294
None => (None, None)
277295
};
278296
if !ctx.dir_only {
297+
let (file_version, product_version) = if ctx.get_version {
298+
pe::read_version_from_dll(path).unwrap_or((None, None))
299+
} else { (None, None) };
279300
write_record(ctx.output_stream,
280301
path.to_string_lossy().as_ref(),
281302
Some(link_target.to_string_lossy().as_ref()),
282303
"file",
283304
l,
284-
modified)?;
305+
modified,
306+
file_version,
307+
product_version)?;
285308
}
286309
Ok((ctx.with_path(parent.as_path()), l.unwrap_or(0)))
287310
}
@@ -323,13 +346,16 @@ fn output_file_info<'a>(
323346
None
324347
};
325348
if !ctx.dir_only {
349+
let (file_version, product_version) = pe::read_version_from_dll(path).unwrap_or((None, None));
326350
write_record(
327351
&mut ctx.output_stream,
328352
path.to_string_lossy().as_ref(),
329353
None,
330354
"file",
331355
len,
332356
modified,
357+
file_version,
358+
product_version
333359
)?;
334360
}
335361
Ok((ctx.with_path(parent.as_path()), len.unwrap_or(0)))
@@ -361,6 +387,8 @@ fn write_record<T>(
361387
file_type: &str,
362388
length: Option<u64>,
363389
last_write: Option<std::time::SystemTime>,
390+
file_version: Option<String>,
391+
product_version: Option<String>
364392
) -> Result<()>
365393
where
366394
T: std::io::Write,
@@ -378,6 +406,8 @@ where
378406
file_type: file_type.to_owned(),
379407
length: length,
380408
last_modified: Some(ststr),
409+
file_version: file_version,
410+
product_version: product_version,
381411
})?;
382412
Ok(())
383413
}
@@ -457,6 +487,8 @@ fn retrieve_symlink<'a>(
457487
"dir",
458488
Some(len),
459489
modified,
490+
None,
491+
None
460492
)?;
461493
}
462494
return Ok((ctx_tmp, len));
@@ -583,6 +615,8 @@ fn enum_files_recursive<'a>(
583615
"dir",
584616
Some(retval.1),
585617
last_write,
618+
None,
619+
None
586620
)?;
587621
}
588622
current_total += retval.1;
@@ -622,7 +656,7 @@ fn enum_files(pattern: &FindOption) -> Result<()> {
622656
}
623657
let (root_ctx, root_size) = enum_files_recursive(ctx, rootpath.as_path(), 0)?;
624658
if root_ctx.output_total {
625-
write_record(&mut record_writer, rootpath.as_path().to_string_lossy().as_ref(), None, "dir", Some(root_size), None)?;
659+
write_record(&mut record_writer, rootpath.as_path().to_string_lossy().as_ref(), None, "dir", Some(root_size), None, None, None)?;
626660
}
627661
}
628662
Ok(())

src/pe.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use std::path::Path;
2+
use anyhow::Result;
3+
use pelite::FileMap;
4+
use pelite::pe64::PeFile as PeFile64;
5+
use pelite::pe64::Pe as Pe64;
6+
use pelite::pe32::PeFile as PeFile32;
7+
use pelite::pe32::Pe as Pe32;
8+
use pelite::Error as PeError;
9+
10+
pub fn read_version_from_dll(p: &Path) -> Result<(Option<String>, Option<String>)> {
11+
let fmap = FileMap::open(p)?;
12+
match PeFile64::from_bytes(&fmap) {
13+
Ok(pe) => {
14+
let resources = pe.resources()?;
15+
return get_versions_from_resource(resources);
16+
},
17+
Err(e) => {
18+
if let PeError::PeMagic = e {
19+
let pe = PeFile32::from_bytes(&fmap)?;
20+
let resources = pe.resources()?;
21+
return get_versions_from_resource(resources);
22+
} else {
23+
return Err(anyhow::Error::from(e));
24+
}
25+
}
26+
};
27+
}
28+
29+
fn get_versions_from_resource(res: pelite::resources::Resources) -> Result<(Option<String>, Option<String>)> {
30+
let verinfo = res.version_info()?;
31+
for lang in verinfo.translation() {
32+
let product = verinfo.value(lang.to_owned(), "ProductVersion");
33+
let file = verinfo.value(lang.to_owned(), "FileVersion");
34+
if product.is_some() || file.is_some() {
35+
return Ok((file.map(|x| x.trim().to_owned()), product.map(|x| x.trim().to_owned())));
36+
}
37+
}
38+
// if no version info
39+
return Ok((None, None));
40+
}

0 commit comments

Comments
 (0)