For next
Advent of Code,
I'm loosely planning to use some old Windows, probably
NT4
(I wanted to go with 2000 first, but that's a bit slow on my old box,
and 95/98 are a bit unstable; NT4 might hit the sweet spot?). What was
missing in my toolchest for that OS was a small program to print the
system's uptime -- just like uptime on Unix. The only way I knew was to
do net statistics server | more and then do the math in your head, but
that's a bit cumbersome.
So, I made
a little tool,
which is nothing more than just a thin wrapper around
GetTickCount().
While at it, I also made an OS/2 version, again just a thin wrapper,
this time around
DosQuerySysInfo(QSV_MS_COUNT, ...).
But can you also do this on DOS?
The little experience I had with this OS family told me that this wasn't going to be straightforward, and browsing through HelpPC didn't make me more optimistic. DOS simply doesn't appear to explicitly keep track of this information.
Table of contents:
NUL (allegedly)
The first thing I found was
UPTIME.PAS by Mark Aitchison.
This program appears to be from 1997, judging by the .timestamps file
in the repo.
So, what does it do? At the core, it's just this:
assign(fyle, 'NUL');
GetFTime(fyle, t);
Since I know very, very little about Pascal (only did some Delphi stuff 30 years ago), I assumed that these two lines meant:
- Open the file
NUL, - get that file's modification date/time .
It would look something like this in C (OpenWatcom v2,
_dos_getftime()):
int main() {
int handle;
unsigned int r, date, time;
if ((r = _dos_open("NUL", O_RDONLY, &handle)) != 0) {
fprintf(stderr, "Could not open NUL: %u\n", r);
return 1;
}
_dos_getftime(handle, &date, &time);
_dos_close(handle);
printf("%04d-%02d-%02d %02d:%02d:%02d\n", YEAR(date), MONTH(date),
DAY(date), HOUR(time), MINUTE(time), SECOND(time));
return 0;
}
But this would only ever print the current date and time for me. Aitchison's program worked, but mine didn't. What's going on?
Reading up on the assign() function:
Assign only operates on the program's internal management data and
has otherwise no immediate observable effect.
[...]
It merely operates on internal management data.
So ... does this actually open the file? Probably not ... ?
Running Aitchison's program in a debugger (with no debugging symbols and in a very glitchy version of Turbo Debugger, which would eat or duplicate key presses all the time -- argh) eventually got me to this point:
It does call INT 21,57, but the BX register is set to zero.
The program still works, though, and prints the correct uptime. My
conclusion here is that it has nothing to do with NUL at all, but
instead reads the "modification time" of stdin.
I changed my C program to just do this:
_dos_getftime(0, &raw_date, &raw_time);
This results in the same INT 21,57 call and it indeed prints the uptime now:
What side effects does this approach have?
To my surprise, things like opening a new command prompt from within
DOSSHELL still shows the correct uptime. But what does break is using
ctty:
Here's what that screenshot shows:
- Running
A:\uptimeprints an uptime of 3 minutes and 37 seconds. Switching to the serial console with
ctty com1. (This is a VM andI connect to that serial console with
telnet localhost 3364on my host.)Running
A:\uptimein the serial console again now shows an uptimeof just 5 seconds.
- Switching back to the VGA console with
ctty conand runningA:\uptimeone last time shows just 2 seconds now.
So there are operations that reinitialize the timestamp of stdin and break this program.
It's also not working on DOSBox, where it just prints garbage:
Similar useless things happen when you run the DOS version on Windows or OS/2, but luckily we have dedicated versions for those systems:
Another program I found was
UPTIMEC by Javier Gutierrez Chamorro,
also in the FreeDOS repo. It's from 2017.
(This one even references the Pascal version and it also claims that
Aitchison's code uses NUL. Is my interpretation wrong or did nobody
notice this before?)
Anyway, Chamorro's version works completely different and bypasses DOS (at least regarding time functions): It uses the Real-Time Clock / CMOS directly. As I understand it, it goes like this:
- When you start it, it asks the CMOS / RTC for the current time and also for the "alarm time".
- It stores the current time as "alarm time" in CMOS memory, if that hasn't been done yet.
- It shows the difference between current time and alarm time as "system's uptime".
So on the very first start, the "uptime" is probably zero -- assuming the
"alarm time" was unset so far. Subsequent runs will then show the
correct result, and things like ctty won't break it.
See also:
This program is arguably better, because it can't break so easily, but it does require a little bit of setup. Here it is running on my Dell Inspiron 6400, right after boot and after the system was powered down:
It shows an uptime of 2 minutes, which isn't correct. The program
couldn't know better -- it just used whatever "alarm time" was stored in
CMOS memory,
and those values are kept between reboots and poweroffs. So the right
thing to do with this tool is to run it in autoexec.bat as ```
uptime
/R
: This resets the value stored in CMOS to the current time, and then
it will show the correct uptime on the next interactive run on the
command line.
Searching the web also reveals this:
[https://stackoverflow.com/a/44718557](https://stackoverflow.com/a/44718557)
The idea is to call
[INT 1A,0](https://helppc.netcore2k.net/interrupt/int-1a-0).
That StackOverflow post also links to
[this page](https://www.oocities.org/geoff_wass/dBASE/GaryWhite/dBASE/FAQ/qmidn.htm)
and quotes a little section from it:
The second problem comes in because of how BIOS int 0x1A operates. Whenever you call this function to retrieve the system time (the current timer tick value) it also returns the current MIDNIGHT flag and RESETS THE FLAG. But since the BIOS function doesn't update the DOS date, the next time you ask for the date, it will not be updated correctly. DOS is aware of the behavior, so when you call any DOS function, the MIDNIGHT flag is maintained correctly. If you call BIOS int 0x1A yourself, you MUST check the MIDNIGHT flag value and turn it back on if it was set.
I only gave this a quick shot. According to the description on HelpPC, this returns the number of seconds since midnight, and that matches my tests. The person on StackOverflow said:
If nobody has *set* this time using `INT 1A,1`, you will get the
seconds since boot.
Hm. I guess someone or something did set it then. :-)
Going back to Chamorro's code: If we need to call `uptime /R` in
`autoexec.bat` ... why even bother with any of this CMOS hackery? Why not
have our little uptime program write *a file* during boot and store the
current time there? Then, subsequent runs will compare the current time
and date to that file?
Of course, this clutters the filesystem. And it's arguably the most expensive solution. But also probably the most solid one? And the most portable one?
In
[my tool](https://movq.de/git/retrouptime/),
I went with the `_dos_getftime(0, ...)` approach for three reasons:
- It doesn't require any setup.
- It's a purely *read-only* operation.
- It appears to be good enough for my purposes.
I really only need this every now and then. And I don't really "need" it, anyway, I'm just curious.
If you know of more, better, different ways, let me know.