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