1 /*
2 * $Id: arjtypes.c,v 1.9 2005/06/23 10:00:54 andrew_belov Exp $
3 * ---------------------------------------------------------------------------
4 * This module provides some multiplatform property types which cover both DOS
5 * (as internally involved in ARJ) and UNIX requirements.
6 *
7 */
8
9 #include <time.h>
10
11 #include "arj.h"
12
13 DEBUGHDR(__FILE__) /* Debug information block */
14
15 /* Timestamp macros */
16
17 #define get_tx(m,d,h,n) (((unsigned long)(m)<<21)+((unsigned long)(d)<<16)+((unsigned long)(h)<<11)+((n)<<5))
18 #define get_tstamp(y,m,d,h,n,s) ((((unsigned long)((y)-1980))<<25)+get_tx((m),(d),(h),(n))+((s)/2))
19
20 #define ts_year(ts) ((unsigned int)(((ts)>>25)&0x7f)+1980)
21 #define ts_month(ts) ((unsigned int)((ts)>>21)&0x0f) /* 1..12 means Jan..Dec */
22 #define ts_day(ts) ((unsigned int)((ts)>>16)&0x1f) /* 1..31 means 1st..31st */
23 #define ts_hour(ts) ((unsigned int)((ts)>>11)&0x1f)
24 #define ts_min(ts) ((unsigned int)((ts)>>5)&0x3f)
25 #define ts_sec(ts) ((unsigned int)(((ts)&0x1f)*2))
26
27 static char monthdays[12]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
28
29 /* Q&D helper macro */
30
31 #define is_unix(host_os) (host_os==OS_UNIX||host_os==OS_NEXT)
32
33 /* Timestamp storage structures */
34
35 #if SFX_LEVEL>=ARJSFX
36 static char time_list_format[]="%04u-%02u-%02u %02u:%02u:%02u";
37 #endif
38
39 /*
40 * File mode routines
41 */
42
43 /* Parses a file mode specifier from the archive. The idea is to allow
44 creation of DOS attributes under UNIX, but no features exist for
45 DOS->UNIX conversion. */
46
fm_store(struct file_mode * dest,int host_os,int mode)47 void fm_store(struct file_mode *dest, int host_os, int mode)
48 {
49 if(host_os==OS_SPECIAL)
50 dest->dos=dest->native=mode;
51 if(is_unix(OS))
52 {
53 dest->native=mode;
54 dest->dos=FATTR_ARCH;
55 if(is_unix(OS)&&!(mode&FATTR_IWUSR))
56 dest->dos|=FATTR_RDONLY;
57 }
58 else /* Assume a DOS-style system */
59 dest->dos=dest->native=mode;
60 }
61
62 /* Retrieves a native file mode corresponding to the host OS */
63
fm_native(struct file_mode * fm,int host_os)64 unsigned int fm_native(struct file_mode *fm, int host_os)
65 {
66 return(is_unix(host_os)?fm->native:fm->dos);
67 }
68
69 /*
70 * Timestamp routines
71 */
72
73 #ifdef LOCALTIME_WORKAROUND
74
75 /* Folds a timestamp into the range handled by LIBC routines, returning a
76 modified timestamp and a number of years to compensate. */
77
fold_timestamp(unsigned long * tt)78 static unsigned int fold_timestamp(unsigned long *tt)
79 {
80 unsigned long v, d;
81
82 d=(unsigned long)(*tt)/86400;
83 if(d>=47482&&d<=47846)
84 {
85 /* 2100 -> 1993 */
86 *tt-=39081*86400UL;
87 return(2100-1993);
88 }
89 else if(d>=47847)
90 {
91 /* 2101... -> 2005... */
92 *tt-=35063*86400UL;
93 return(2101-2005);
94 }
95 else
96 {
97 /* Wrap into 28-year cycles (1970...1997) */
98 v=((unsigned long)*tt)/((28*365+7)*86400);
99 *tt=((unsigned long)*tt)%((28*365+7)*86400);
100 return(v*28);
101 }
102 /* NOTREACHED */
103 }
104
105 /* The ARJ workaround for localtime */
106
arj_localtime(const time_t * ts)107 struct tm *arj_localtime(const time_t *ts)
108 {
109 unsigned int v;
110 unsigned long tt;
111 struct tm *rc;
112
113 tt=*ts;
114 v=fold_timestamp(&tt);
115 rc=localtime((time_t *)&tt);
116 if(rc!=NULL)
117 rc->tm_year+=v;
118 return(rc);
119 }
120
121 #endif
122
123 /* Returns 1 if there's a leap year */
124
isleapyear(int year)125 static int isleapyear(int year)
126 {
127 if(year%400==0)
128 return(1);
129 if(year%100==0)
130 return(0);
131 if(year%4==0)
132 return(1);
133 return(0);
134 }
135
136 /* Converts a UNIX timestamp to the DOS style */
137
ts_unix2dos(time_t ts)138 static unsigned long ts_unix2dos(time_t ts)
139 {
140 struct tm *stm;
141 time_t _ts;
142
143 _ts = ts;
144
145 stm=arj_localtime(&_ts);
146 return(get_tstamp(stm->tm_year+1900, stm->tm_mon+1, stm->tm_mday,
147 stm->tm_hour, stm->tm_min, stm->tm_sec));
148 }
149
150 /* Creates a Unix timestamp from the given date and time */
151
mk_unixtime(int y,int m,int d,int hh,int mm,int ss)152 static unsigned long mk_unixtime(int y, int m, int d, int hh, int mm, int ss)
153 {
154 unsigned long u=0;
155 unsigned int i, v;
156 /* Clash with NetBSD/x86-64 patch: leaving rc as unsigned long still permits
157 to escape the year 2038 problem in favor of year 2106 problem, while a
158 dedicated time_t structure can be expected as a 64-bit value on relevant
159 platforms -- ASR fix 25/01/2004 */
160 time_t rc;
161 time_t tt, ts;
162 long tzshift, shiftd1, shiftd2;
163 struct tm *stm;
164
165 if(y>=2001)
166 {
167 i=y-2001;
168 u=11323;
169 /* The following piece of code is rather paranoid in 16/32-bit world, where the
170 timestamps are limited to year 2108. */
171 #if defined(__32BIT__)||defined(TILED)
172 if(i>=400)
173 {
174 u+=1022679L*(i/400);
175 i%=400;
176 }
177 #endif
178 if(i>=100)
179 {
180 u+=36524L*(i/100);
181 i%=100;
182 }
183 u+=1461L*(i/4);
184 u+=365L*(i%4);
185 }
186 else if(y>=1973)
187 u=1096+(y-1973)/4*1461L+((y-1973)%4)*365L;
188 else
189 u=(y-1970)*365L;
190 for(i=1; i<m; i++)
191 {
192 u+=(int)monthdays[i-1];
193 if(i==2)
194 u+=isleapyear(y);
195 }
196 rc=86400*(unsigned long)(u+d-1)+(unsigned long)hh*3600+(unsigned long)mm*60+(unsigned long)ss;
197 stm=arj_localtime(&rc);
198 debug_assert(stm!=NULL); /* LIBCS.DLL returns NULL for unixtime beyond
199 0x7FFFFFFF */
200 tzshift=(long)stm->tm_hour*3600+(long)stm->tm_min*60;
201 shiftd1=stm->tm_mday;
202 ts=rc;
203 #ifdef LOCALTIME_WORKAROUND
204 v=fold_timestamp(&ts);
205 stm=gmtime((const long *)&ts);
206 debug_assert(stm!=NULL);
207 stm->tm_year+=v;
208 #else
209 stm=gmtime(&ts);
210 #endif
211 shiftd2=stm->tm_mday;
212 /* Local time overruns GMT, add 24 hours for safety */
213 if(shiftd1<shiftd2&&shiftd1==1&&shiftd2>=28)
214 tzshift+=86400;
215 else if(shiftd1>shiftd2&&shiftd1>=28&&shiftd2==1)
216 tzshift-=86400;
217 else if(shiftd1>shiftd2)
218 tzshift+=86400;
219 else if(shiftd1<shiftd2)
220 tzshift-=86400;
221 tzshift-=(long)stm->tm_hour*3600+(long)stm->tm_min*60;
222 tzshift%=86400;
223 /* Fix the timezone if it does not roll over the zero */
224 return((tzshift>0&&rc<tzshift)?rc:rc-tzshift);
225 }
226
227 /* Converts a DOS timestamp to the UNIX representation */
228
ts_dos2unix(unsigned long ts)229 static unsigned long ts_dos2unix(unsigned long ts)
230 {
231 unsigned int y, m, d, hh, mm, ss;
232
233 if(ts==0)
234 return(0);
235 y=ts_year(ts);
236 m=ts_month(ts);
237 d=ts_day(ts);
238 hh=ts_hour(ts);
239 mm=ts_min(ts);
240 ss=ts_sec(ts);
241 /* TODO: These assertions must be replaced by run-time check for incorrect timestamps
242 like 31/15/2063 or 00/00/1980, since month array is 1...12 only. */
243 #ifdef DEBUG
244 debug_assert(m>=1&&m<=12);
245 debug_assert(d>=1&&d<=31);
246 #endif
247 return(mk_unixtime(y, m, d, hh, mm, ss));
248 }
249
250 /* Stores a timestamp */
251
ts_store(struct timestamp * dest,int host_os,unsigned long value)252 void ts_store(struct timestamp *dest, int host_os, unsigned long value)
253 {
254 if(host_os==OS_SPECIAL)
255 dest->dos=dest->unixtime=value;
256 else if(is_unix(host_os))
257 {
258 dest->unixtime=value;
259 dest->dos=ts_unix2dos(value);
260 }
261 else
262 {
263 dest->dos=value;
264 dest->unixtime=ts_dos2unix(value);
265 }
266 }
267
268 /* Retrieves a native timestamp corresponding to the host OS */
269
ts_native(struct timestamp * ts,int host_os)270 unsigned long ts_native(struct timestamp *ts, int host_os)
271 {
272 return(is_unix(host_os)?ts->unixtime:ts->dos);
273 }
274
275 /* Compares two timestamps */
276
ts_cmp(struct timestamp * ts1,struct timestamp * ts2)277 int ts_cmp(struct timestamp *ts1, struct timestamp *ts2)
278 {
279 unsigned long tsn1, tsn2;
280
281 tsn1=ts_native(ts1, OS);
282 tsn2=ts_native(ts2, OS);
283 if(tsn1<tsn2)
284 return(-1);
285 else if(tsn1==tsn2)
286 return(0);
287 else
288 return(1);
289 }
290
291 #if SFX_LEVEL>=ARJ||defined(REARJ)
292
293 /* Produces an ARJ timestamp from the given date */
294
make_timestamp(struct timestamp * dest,int y,int m,int d,int hh,int mm,int ss)295 void make_timestamp(struct timestamp *dest, int y, int m, int d, int hh, int mm, int ss)
296 {
297 dest->unixtime=mk_unixtime(y, m, d, hh, mm, ss);
298 dest->dos=ts_unix2dos(dest->unixtime);
299 }
300
301 #endif
302
303 #if SFX_LEVEL>=ARJSFX
304
305 /* Restores the given timestamp to character form */
306
timestamp_to_str(char * str,struct timestamp * ts)307 void timestamp_to_str(char *str, struct timestamp *ts)
308 {
309 struct tm *stm;
310 time_t ut = ts->unixtime;
311
312 stm=arj_localtime(&ut);
313 /* Workaround for a MS C v 7.0 CRT bug */
314 #if TARGET==DOS&&COMPILER==MSC&&_MSC_VER==700
315 if(stm->tm_year<70) /* 31 -> 101 */
316 stm->tm_year+=70;
317 #endif
318 sprintf(str, time_list_format, stm->tm_year+1900, stm->tm_mon+1, stm->tm_mday,
319 stm->tm_hour, stm->tm_min, stm->tm_sec);
320 }
321
322 #endif
323