Skip to content

Timestamp support#187

Open
TheComputerGuy96 wants to merge 3 commits intotwogood:mainfrom
TheComputerGuy96:timestamp-support
Open

Timestamp support#187
TheComputerGuy96 wants to merge 3 commits intotwogood:mainfrom
TheComputerGuy96:timestamp-support

Conversation

@TheComputerGuy96
Copy link

Fixes #100

The timestamp setting code currently doesn't use the file wrappers though (and is directly located in unshield.c)

@twogood
Copy link
Owner

twogood commented Dec 14, 2024

Very nice! What work is need to support it in the wrapper?

@twogood twogood self-assigned this Dec 14, 2024
@TheComputerGuy96
Copy link
Author

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)
@TheComputerGuy96 TheComputerGuy96 force-pushed the timestamp-support branch 2 times, most recently from 5a67093 to b3c5979 Compare January 9, 2026 14:57
@TheComputerGuy96
Copy link
Author

After looking some more, I noticed the unshield binary uses direct stat() calls (so function wrappers may not be needed there)

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

@twogood
Copy link
Owner

twogood commented Jan 9, 2026

After looking some more, I noticed the unshield binary uses direct stat() calls (so function wrappers may not be needed there)

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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 -t command-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.

@twogood
Copy link
Owner

twogood commented Jan 11, 2026

@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.

TheComputerGuy96 and others added 2 commits January 14, 2026 12:07
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>
@TheComputerGuy96
Copy link
Author

do you think the Copilot comments have any merit?

@twogood I added extra safety checks in the new PR version (which should mostly appease Copilot)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +70 to +72
fd->dos_date = (uint16_t)READ_UINT32(p); p += 4;
fd->dos_time = (uint16_t)READ_UINT32(p); p += 4;
p += 8;
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
dirname = malloc(path_max);
filename = malloc(path_max);
full_filename = malloc(path_max);
if (real_output_directory == NULL || real_filename == NULL)
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if (real_output_directory == NULL || real_filename == NULL)
if (real_output_directory == NULL || real_filename == NULL ||
dirname == NULL || full_filename == NULL)

Copilot uses AI. Check for mistakes.
Comment on lines +561 to +577
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);
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +559 to +578
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);
}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);

Copilot uses AI. Check for mistakes.
Comment on lines +556 to +573
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);
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +552 to +579
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);
}
}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Request] Timestamp preservation

2 participants