Conversation
|
Very nice! What work is need to support it in the wrapper? |
I think it's as easy as adding the relevant callbacks (but I'm not sure if those 2 functions have alternative implementations; especially the utime() function) |
These were originally present in the early unshield code but for some reason they were removed later (this change is based on the i5comp and i6comp struct layout) Those struct members have a correct date with the Baldur's Gate test (likely <IS6?) and GTA San Andreas (IS9)
5a67093 to
b3c5979
Compare
|
After looking some more, I noticed the Also while the timestamp setting code could be moved to another function or as a part of common extract code, I don't think it's that important |
I thought of including it in function wrappers for consistency. |
There was a problem hiding this comment.
Pull request overview
This pull request adds timestamp support to the unshield tool, allowing users to preserve the original modification date/time from InstallShield metadata when extracting files. This addresses issue #100.
Key Changes:
- Adds a new
-tcommand-line flag to enable timestamp preservation during extraction - Implements DOS timestamp parsing and conversion to Unix time_t
- Updates file listing to display timestamps in ISO 8601 format
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/unshield.c | Adds -t option handling, timestamp setting logic in extract_file(), and date display in file listing |
| man/unshield.1 | Documents the new -t option and updates man page date to December 2024 |
| lib/libunshield.h | Declares two new public API functions: unshield_file_iso_date() and unshield_file_timestamp() |
| lib/internal.h | Declares internal helper function unshield_dos_to_tm() for DOS timestamp conversion |
| lib/helper.c | Implements unshield_dos_to_tm() to convert DOS date/time format to struct tm |
| lib/file.c | Implements the new API functions and adds DOS date/time parsing in file descriptor reading |
| lib/cabfile.h | Adds dos_date and dos_time fields to FileDescriptor structure |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@TheComputerGuy96 not meaning to give you extra work but do you think the Copilot comments have any merit? It could also be possible to merge this PR and address those comments in a second PR. |
This also adds a helper function to load the DOS date/time into a tm structure and an library function to get the ISO 8601 date Co-authored-by: GitHub Copilot <copilot@github.com>
This also adds an API function for getting an Unix timestamp (and updates the man page) Co-authored-by: GitHub Copilot <copilot@github.com>
b3c5979 to
a83e977
Compare
@twogood I added extra safety checks in the new PR version (which should mostly appease Copilot) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fd->dos_date = (uint16_t)READ_UINT32(p); p += 4; | ||
| fd->dos_time = (uint16_t)READ_UINT32(p); p += 4; | ||
| p += 8; |
There was a problem hiding this comment.
The code reads dos_date and dos_time as 32-bit values using READ_UINT32 and then casts them to 16-bit. This seems inconsistent with the later versions (6+) which read them as 16-bit values directly. If the file format actually stores these as 32-bit values in versions 0 and 5, consider documenting why this is the case. If they are actually 16-bit fields, use READ_UINT16 instead and adjust the pointer increment accordingly. The current approach may work but could be confusing and potentially read incorrect data depending on endianness and padding.
| dirname = malloc(path_max); | ||
| filename = malloc(path_max); | ||
| full_filename = malloc(path_max); | ||
| if (real_output_directory == NULL || real_filename == NULL) |
There was a problem hiding this comment.
The NULL check only verifies real_output_directory and real_filename, but doesn't check the newly allocated dirname and full_filename pointers. If malloc fails for these variables, they will be NULL and dereferencing them later will cause a segmentation fault. The NULL check should include dirname and full_filename as well.
| if (real_output_directory == NULL || real_filename == NULL) | |
| if (real_output_directory == NULL || real_filename == NULL || | |
| dirname == NULL || full_filename == NULL) |
| fprintf(stderr, "Failed to get timestamp for file '%s'.", filename); | ||
| } | ||
| else | ||
| { | ||
| memset(&utim, 0, sizeof(utim)); | ||
| memset(&info, 0, sizeof(info)); | ||
|
|
||
| ret = stat(full_filename, &info); | ||
| if (ret == 0) | ||
| { | ||
| utim.actime = info.st_atime; | ||
| utim.modtime = timestamp; | ||
| ret2 = utime(full_filename, &utim); | ||
| } | ||
|
|
||
| if (ret < 0 || ret2 < 0) | ||
| fprintf(stderr, "Failed to set timestamp for file '%s'.", filename); |
There was a problem hiding this comment.
The error messages on lines 561 and 577 are missing a newline character at the end, which is inconsistent with other error messages in this file and can make the output harder to read. Add '\n' at the end of both error messages.
| if (!timestamp) | ||
| { | ||
| fprintf(stderr, "Failed to get timestamp for file '%s'.", filename); | ||
| } | ||
| else | ||
| { | ||
| memset(&utim, 0, sizeof(utim)); | ||
| memset(&info, 0, sizeof(info)); | ||
|
|
||
| ret = stat(full_filename, &info); | ||
| if (ret == 0) | ||
| { | ||
| utim.actime = info.st_atime; | ||
| utim.modtime = timestamp; | ||
| ret2 = utime(full_filename, &utim); | ||
| } | ||
|
|
||
| if (ret < 0 || ret2 < 0) | ||
| fprintf(stderr, "Failed to set timestamp for file '%s'.", filename); | ||
| } |
There was a problem hiding this comment.
The check for a zero timestamp may not be appropriate here. A timestamp of 0 (Unix epoch: January 1, 1970) is a valid timestamp value, though unlikely for InstallShield files. The code should distinguish between a valid timestamp of 0 and an error condition. Consider checking if unshield_file_timestamp returned 0 due to an error versus having an actual 0 timestamp (which would be a valid DOS date/time of 00:00:00 on an invalid date). The current implementation will skip setting timestamps for files that legitimately have a timestamp of 0.
| if (!timestamp) | |
| { | |
| fprintf(stderr, "Failed to get timestamp for file '%s'.", filename); | |
| } | |
| else | |
| { | |
| memset(&utim, 0, sizeof(utim)); | |
| memset(&info, 0, sizeof(info)); | |
| ret = stat(full_filename, &info); | |
| if (ret == 0) | |
| { | |
| utim.actime = info.st_atime; | |
| utim.modtime = timestamp; | |
| ret2 = utime(full_filename, &utim); | |
| } | |
| if (ret < 0 || ret2 < 0) | |
| fprintf(stderr, "Failed to set timestamp for file '%s'.", filename); | |
| } | |
| memset(&utim, 0, sizeof(utim)); | |
| memset(&info, 0, sizeof(info)); | |
| ret = stat(full_filename, &info); | |
| if (ret == 0) | |
| { | |
| utim.actime = info.st_atime; | |
| utim.modtime = timestamp; | |
| ret2 = utime(full_filename, &utim); | |
| } | |
| if (ret < 0 || ret2 < 0) | |
| fprintf(stderr, "Failed to set timestamp for file '%s'.", filename); |
| struct utimbuf utim; | ||
| struct stat info; | ||
|
|
||
| if (!timestamp) | ||
| { | ||
| fprintf(stderr, "Failed to get timestamp for file '%s'.", filename); | ||
| } | ||
| else | ||
| { | ||
| memset(&utim, 0, sizeof(utim)); | ||
| memset(&info, 0, sizeof(info)); | ||
|
|
||
| ret = stat(full_filename, &info); | ||
| if (ret == 0) | ||
| { | ||
| utim.actime = info.st_atime; | ||
| utim.modtime = timestamp; | ||
| ret2 = utime(full_filename, &utim); |
There was a problem hiding this comment.
The code uses POSIX utime() and struct utimbuf without Windows-specific handling. On Windows, these may not be available or may need to use _utime() and struct _utimbuf instead. Consider adding platform-specific code or using a more portable approach like utimensat/futimens on systems that support them, or adding Windows-specific equivalents with conditional compilation.
| if (success && set_timestamp) | ||
| { | ||
| time_t timestamp = unshield_file_timestamp(unshield, index); | ||
| int ret = 0, ret2 = 0; | ||
| struct utimbuf utim; | ||
| struct stat info; | ||
|
|
||
| if (!timestamp) | ||
| { | ||
| fprintf(stderr, "Failed to get timestamp for file '%s'.", filename); | ||
| } | ||
| else | ||
| { | ||
| memset(&utim, 0, sizeof(utim)); | ||
| memset(&info, 0, sizeof(info)); | ||
|
|
||
| ret = stat(full_filename, &info); | ||
| if (ret == 0) | ||
| { | ||
| utim.actime = info.st_atime; | ||
| utim.modtime = timestamp; | ||
| ret2 = utime(full_filename, &utim); | ||
| } | ||
|
|
||
| if (ret < 0 || ret2 < 0) | ||
| fprintf(stderr, "Failed to set timestamp for file '%s'.", filename); | ||
| } | ||
| } |
There was a problem hiding this comment.
The new timestamp setting functionality (-t flag) lacks test coverage. Consider adding tests that verify: 1) timestamps are correctly set when the -t flag is used, 2) extraction still works when timestamps cannot be set, 3) the timestamp values match expected DOS date/time conversions. This would help ensure the feature works correctly across different platforms and edge cases.
Fixes #100
The timestamp setting code currently doesn't use the file wrappers though (and is directly located in unshield.c)