b69e3e0f1a1798f39bef4579bd30a0c9a29ce0d2
[dragonfly.git] / sys / vfs / nwfs / nwfs_subr.c
1 /*
2  * Copyright (c) 1999, Boris Popov
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *    This product includes software developed by Boris Popov.
16  * 4. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * $FreeBSD: src/sys/nwfs/nwfs_subr.c,v 1.2.2.2 2000/10/25 02:11:10 bp Exp $
33  * $DragonFly: src/sys/vfs/nwfs/nwfs_subr.c,v 1.8 2006/12/23 00:41:30 swildner Exp $
34  */
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/kernel.h>
38 #include <sys/malloc.h>
39 #include <machine/clock.h>
40 #include <sys/time.h>
41
42 #include <netproto/ncp/ncp.h>
43 #include <netproto/ncp/ncp_conn.h>
44 #include <netproto/ncp/ncp_ncp.h>
45 #include <netproto/ncp/ncp_subr.h>
46 #include <netproto/ncp/ncp_rq.h>
47 #include <netproto/ncp/nwerror.h>
48
49 #include "nwfs.h"
50 #include "nwfs_node.h"
51 #include "nwfs_subr.h"
52
53 MALLOC_DEFINE(M_NWFSDATA, "NWFS data", "NWFS private data");
54
55 static void 
56 ncp_extract_file_info(struct nwmount *nmp, struct ncp_rq *rqp,
57                       struct nw_entry_info *target)
58 {
59         u_char name_len;
60         const int info_struct_size = sizeof(struct nw_entry_info) - 257;
61
62         ncp_rp_mem(rqp,(caddr_t)target,info_struct_size);
63         name_len = ncp_rp_byte(rqp);
64         target->nameLen = name_len;
65         ncp_rp_mem(rqp,(caddr_t)target->entryName, name_len);
66         target->entryName[name_len] = '\0';
67         ncp_path2unix(target->entryName, target->entryName, name_len, &nmp->m.nls);
68         return;
69 }
70
71 static void 
72 ncp_update_file_info(struct nwmount *nmp, struct ncp_rq *rqp, 
73                      struct nw_entry_info *target)
74 {
75         int info_struct_size = sizeof(struct nw_entry_info) - 257;
76
77         ncp_rp_mem(rqp,(caddr_t)target,info_struct_size);
78         return;
79 }
80
81 int
82 ncp_initsearch(struct vnode *dvp, struct thread *td, struct ucred *cred)
83 {
84         struct nwmount *nmp = VTONWFS(dvp);
85         struct ncp_conn *conn = NWFSTOCONN(nmp);
86         struct nwnode *np = VTONW(dvp);
87         u_int8_t volnum = nmp->n_volume;
88         u_int32_t dirent = np->n_fid.f_id;
89         int error;
90         DECLARE_RQ;
91
92         NCPNDEBUG("vol=%d,dir=%d\n", volnum, dirent);
93         NCP_RQ_HEAD(87,td,cred);
94         ncp_rq_byte(rqp, 2);            /* subfunction */
95         ncp_rq_byte(rqp, nmp->name_space);
96         ncp_rq_byte(rqp, 0);            /* reserved */
97         ncp_rq_dbase_path(rqp, volnum, dirent, 0, NULL, NULL);
98         checkbad(ncp_request(conn,rqp));
99         ncp_rp_mem(rqp,(caddr_t)&np->n_seq, sizeof(np->n_seq));
100         NCP_RQ_EXIT;
101         return error;
102 }
103
104 int 
105 ncp_search_for_file_or_subdir(struct nwmount *nmp,
106                               struct nw_search_seq *seq,
107                               struct nw_entry_info *target,
108                               struct thread *td, struct ucred *cred)
109 {
110         struct ncp_conn *conn = NWFSTOCONN(nmp);
111         int error;
112         DECLARE_RQ;
113
114         NCP_RQ_HEAD(87,td,cred);
115         ncp_rq_byte(rqp, 3);            /* subfunction */
116         ncp_rq_byte(rqp, nmp->name_space);
117         ncp_rq_byte(rqp, 0);            /* data stream */
118         ncp_rq_word_lh(rqp, 0xffff);    /* Search attribs */
119         ncp_rq_dword(rqp, IM_ALL);      /* return info mask */
120         ncp_rq_mem(rqp, (caddr_t)seq, 9);
121         ncp_rq_byte(rqp, 2);            /* 2 byte pattern */
122         ncp_rq_byte(rqp, 0xff);         /* following is a wildcard */
123         ncp_rq_byte(rqp, '*');
124         checkbad(ncp_request(conn,rqp));
125         ncp_rp_mem(rqp,(caddr_t)seq, sizeof(*seq));
126         ncp_rp_byte(rqp);               /* skip */
127         ncp_extract_file_info(nmp, rqp, target);
128         NCP_RQ_EXIT;
129         return error;
130 }
131
132 /*
133  * Returns information for a (one-component) name relative to the specified
134  * directory.
135  */
136 int 
137 ncp_obtain_info(struct nwmount *nmp, u_int32_t dirent,
138                 int namelen, char *path, struct nw_entry_info *target,
139                 struct thread *td, struct ucred *cred)
140 {
141         struct ncp_conn *conn=NWFSTOCONN(nmp);
142         int error;
143         u_char volnum = nmp->n_volume, ns;
144         DECLARE_RQ;
145
146         if (target == NULL) {
147                 NCPFATAL("target == NULL\n");
148                 return EINVAL;
149         }
150         ns = (path == NULL || path[0] == 0) ? NW_NS_DOS : nmp->name_space;
151         NCP_RQ_HEAD(87, td, cred);
152         ncp_rq_byte(rqp, 6);                    /* subfunction */
153         ncp_rq_byte(rqp, ns);
154         ncp_rq_byte(rqp, ns);   /* DestNameSpace */
155         ncp_rq_word(rqp, htons(0xff00));        /* get all */
156         ncp_rq_dword(rqp, IM_ALL);
157         ncp_rq_dbase_path(rqp, volnum, dirent, namelen, path, &nmp->m.nls);
158         checkbad(ncp_request(conn,rqp));
159         if (path)
160                 ncp_extract_file_info(nmp, rqp, target);
161         else
162                 ncp_update_file_info(nmp, rqp, target);
163         NCP_RQ_EXIT;
164         return error;
165 }
166 /* 
167  * lookup name pointed by cnp in directory dvp and return file info in np.
168  * May be I should create a little cache, but another way is to minimize
169  * number of calls, on other hand, in multiprocess environment ...
170  */
171 int
172 ncp_lookup(struct vnode *dvp, int len, char *name, struct nw_entry_info *fap,
173            struct thread *td, struct ucred *cred)
174 {
175         struct nwmount *nmp;
176         struct nwnode *dnp = VTONW(dvp);
177         struct ncp_conn *conn;
178         int error;
179
180         if (!dvp || dvp->v_type != VDIR) {
181                 nwfs_printf("dvp is NULL or not a directory.\n");
182                 return (ENOENT);
183         }
184         nmp = VTONWFS(dvp);
185         conn = NWFSTOCONN(nmp);
186
187         if (len == 1 && name[0] == '.') {
188                 if (strcmp(dnp->n_name, NWFS_ROOTVOL) == 0) {
189                         error = ncp_obtain_info(nmp, dnp->n_fid.f_id, 0, NULL,
190                                 fap, td, cred);
191                 } else {
192                         error = ncp_obtain_info(nmp, dnp->n_fid.f_parent, 
193                                 dnp->n_nmlen, dnp->n_name, fap, td, cred);
194                 }
195                 return error;
196         } else if (len == 2 && name[0] == '.' && name[1] == '.') {
197                 kprintf("%s: knows NOTHING about '..'\n", __func__);
198                 return EIO;
199         } else {
200                 error = ncp_obtain_info(nmp, dnp->n_fid.f_id, 
201                         len, name, fap, td, cred);
202         }
203         return error;
204 }
205
206 static void ConvertToNWfromDWORD(u_int32_t sfd, ncp_fh *fh);
207 static void 
208 ConvertToNWfromDWORD(u_int32_t sfd, ncp_fh *fh) {
209         fh->val1 = (fh->val.val32 = sfd);
210         return;
211 }
212
213 /*
214  * If both dir and name are NULL, then in target there's already a looked-up
215  * entry that wants to be opened.
216  */
217 int 
218 ncp_open_create_file_or_subdir(struct nwmount *nmp, struct vnode *dvp,
219                                int namelen, char *name, int open_create_mode,
220                                u_int32_t create_attributes,
221                                int desired_acc_rights,
222                                struct ncp_open_info *nop,
223                                struct thread *td, struct ucred *cred)
224 {
225         
226         struct ncp_conn *conn=NWFSTOCONN(nmp);
227         u_int16_t search_attribs = SA_ALL & (~SA_SUBDIR_FILES);
228         u_int8_t volnum;
229         u_int32_t dirent;
230         int error;
231         DECLARE_RQ;
232
233         volnum = nmp->n_volume;
234         dirent = VTONW(dvp)->n_fid.f_id;
235         if ((create_attributes & aDIR) != 0) {
236                 search_attribs |= SA_SUBDIR_FILES;
237         }
238         NCP_RQ_HEAD(87,td,cred);
239         ncp_rq_byte(rqp, 1);/* subfunction */
240         ncp_rq_byte(rqp, nmp->name_space);
241         ncp_rq_byte(rqp, open_create_mode);
242         ncp_rq_word(rqp, search_attribs);
243         ncp_rq_dword(rqp, IM_ALL);
244         ncp_rq_dword(rqp, create_attributes);
245         /*
246          * The desired acc rights seem to be the inherited rights mask for
247          * directories
248          */
249         ncp_rq_word(rqp, desired_acc_rights);
250         ncp_rq_dbase_path(rqp, volnum, dirent, namelen, name, &nmp->m.nls);
251         checkbad(ncp_request(conn,rqp));
252
253         nop->origfh = ncp_rp_dword_lh(rqp);
254         nop->action = ncp_rp_byte(rqp);
255         ncp_rp_byte(rqp);       /* skip */
256         ncp_extract_file_info(nmp, rqp, &nop->fattr);
257         ConvertToNWfromDWORD(nop->origfh, &nop->fh);
258         NCP_RQ_EXIT;
259         switch(error) {
260             case NWE_FILE_NO_CREATE_PRIV:
261                 error = EACCES;
262                 break;
263         }
264         return error;
265 }
266
267 int
268 ncp_close_file(struct ncp_conn *conn, ncp_fh *fh, struct thread *td,
269                struct ucred *cred)
270 {
271         int error;
272         DECLARE_RQ;
273
274         NCP_RQ_HEAD(66,td,cred);
275         ncp_rq_byte(rqp, 0);
276         ncp_rq_mem(rqp, (caddr_t)fh, 6);
277         error = ncp_request(conn,rqp);
278         NCP_RQ_EXIT_NB;
279         return error;
280 }
281
282 int
283 ncp_DeleteNSEntry(struct nwmount *nmp, u_int32_t dirent, int namelen,
284                   char *name, struct thread *td, struct ucred *cred)
285 {
286         int error;
287         struct ncp_conn *conn=NWFSTOCONN(nmp);
288         DECLARE_RQ;
289
290         NCP_RQ_HEAD(87,td,cred);
291         ncp_rq_byte(rqp, 8);            /* subfunction */
292         ncp_rq_byte(rqp, nmp->name_space);
293         ncp_rq_byte(rqp, 0);            /* reserved */
294         ncp_rq_word(rqp, SA_ALL);       /* search attribs: all */
295         ncp_rq_dbase_path(rqp, nmp->n_volume, dirent, namelen, name, &nmp->m.nls);
296         error = ncp_request(conn,rqp);
297         NCP_RQ_EXIT_NB;
298         return error;
299 }
300
301 int 
302 ncp_nsrename(struct ncp_conn *conn, int volume, int ns, int oldtype, 
303              struct ncp_nlstables *nt, nwdirent fdir, char *old_name,
304              int oldlen, nwdirent tdir, char *new_name, int newlen,
305              struct thread *td, struct ucred *cred)
306 {
307         DECLARE_RQ;
308         int error;
309
310         NCP_RQ_HEAD(87,td,cred);
311         ncp_rq_byte(rqp, 4);
312         ncp_rq_byte(rqp, ns);
313         ncp_rq_byte(rqp, 1);
314         ncp_rq_word(rqp, oldtype);
315         /* source Handle Path */
316         ncp_rq_byte(rqp, volume);
317         ncp_rq_dword(rqp, fdir);
318         ncp_rq_byte(rqp, 1);
319         ncp_rq_byte(rqp, 1);    /* 1 source component */
320         /* dest Handle Path */
321         ncp_rq_byte(rqp, volume);
322         ncp_rq_dword(rqp, tdir);
323         ncp_rq_byte(rqp, 1);
324         ncp_rq_byte(rqp, 1);    /* 1 destination component */
325         ncp_rq_pathstring(rqp, oldlen, old_name, nt);
326         ncp_rq_pathstring(rqp, newlen, new_name, nt);
327         error = ncp_request(conn,rqp);
328         NCP_RQ_EXIT_NB;
329         return error;
330 }
331
332 int
333 ncp_modify_file_or_subdir_dos_info(struct nwmount *nmp, struct vnode *vp, 
334                                    u_int32_t info_mask,
335                                    struct nw_modify_dos_info *info,
336                                    struct thread *td, struct ucred *cred)
337 {
338         struct nwnode *np=VTONW(vp);
339         u_int8_t volnum = nmp->n_volume;
340         u_int32_t dirent = np->n_fid.f_id;
341         struct ncp_conn *conn=NWFSTOCONN(nmp);
342         int             error;
343         DECLARE_RQ;
344
345         NCP_RQ_HEAD(87,td,cred);
346         ncp_rq_byte(rqp, 7);    /* subfunction */
347         ncp_rq_byte(rqp, nmp->name_space);
348         ncp_rq_byte(rqp, 0);    /* reserved */
349         ncp_rq_word(rqp, htons(0x0680));        /* search attribs: all */
350         ncp_rq_dword(rqp, info_mask);
351         ncp_rq_mem(rqp, (caddr_t)info, sizeof(*info));
352         ncp_rq_dbase_path(rqp, volnum, dirent, 0, NULL, NULL);
353         error = ncp_request(conn,rqp);
354         NCP_RQ_EXIT_NB;
355         return error;
356 }
357
358 int
359 ncp_setattr(struct vnode *vp, struct vattr *vap, struct ucred *cred,
360             struct thread *td)
361 {
362         struct nwmount *nmp=VTONWFS(vp);
363         struct nwnode *np=VTONW(vp);
364         struct ncp_open_info nwn;
365         struct ncp_conn *conn=NWFSTOCONN(nmp);
366         struct nw_modify_dos_info info;
367         int error = 0, info_mask;
368         DECLARE_RQ;
369
370         if (vap->va_size != VNOVAL) {
371                 error = ncp_open_create_file_or_subdir(
372                             nmp, vp, 0, NULL, OC_MODE_OPEN, 0,
373                             AR_WRITE | AR_READ, &nwn,td,cred);
374                 if (error) return error;
375                 NCP_RQ_HEAD(73,td,cred);
376                 ncp_rq_byte(rqp, 0);
377                 ncp_rq_mem(rqp, (caddr_t)&nwn.fh, 6);
378                 ncp_rq_dword(rqp, htonl(vap->va_size));
379                 ncp_rq_word_hl(rqp, 0);
380                 checkbad(ncp_request(conn,rqp));
381                 np->n_vattr.va_size = np->n_size = vap->va_size;
382                 NCP_RQ_EXIT;
383                 ncp_close_file(conn, &nwn.fh, td, cred);
384                 if (error) return error;
385         }
386         info_mask = 0;
387         bzero(&info, sizeof(info));
388
389         if (vap->va_mtime.tv_sec != VNOVAL) {
390                 info_mask |= (DM_MODIFY_TIME | DM_MODIFY_DATE);
391                 ncp_unix2dostime(&vap->va_mtime, nmp->m.tz, &info.modifyDate, &info.modifyTime, NULL);
392         }
393         if (vap->va_atime.tv_sec != VNOVAL) {
394                 info_mask |= (DM_LAST_ACCESS_DATE);
395                 ncp_unix2dostime(&vap->va_atime, nmp->m.tz, &info.lastAccessDate, NULL, NULL);
396         }
397         if (info_mask) {
398                 error = ncp_modify_file_or_subdir_dos_info(nmp, vp, info_mask, &info,td,cred);
399         }
400         return (error);
401 }
402
403 int
404 ncp_get_volume_info_with_number(struct ncp_conn *conn, int n,
405                                 struct ncp_volume_info *target,
406                                 struct thread *td, struct ucred *cred)
407 {
408         int error,len;
409         DECLARE_RQ;
410
411         NCP_RQ_HEAD_S(22,44,td,cred);
412         ncp_rq_byte(rqp,n);
413         checkbad(ncp_request(conn,rqp));
414         target->total_blocks = ncp_rp_dword_lh(rqp);
415         target->free_blocks = ncp_rp_dword_lh(rqp);
416         target->purgeable_blocks = ncp_rp_dword_lh(rqp);
417         target->not_yet_purgeable_blocks = ncp_rp_dword_lh(rqp);
418         target->total_dir_entries = ncp_rp_dword_lh(rqp);
419         target->available_dir_entries = ncp_rp_dword_lh(rqp);
420         ncp_rp_dword_lh(rqp);
421         target->sectors_per_block = ncp_rp_byte(rqp);
422         bzero(&target->volume_name, sizeof(target->volume_name));
423         len = ncp_rp_byte(rqp);
424         if (len > NCP_VOLNAME_LEN) {
425                 error = ENAMETOOLONG;
426         } else {
427                 ncp_rp_mem(rqp,(caddr_t)&target->volume_name, len);
428         }
429         NCP_RQ_EXIT;
430         return error;
431 }
432
433 int
434 ncp_get_namespaces(struct ncp_conn *conn, u_int32_t volume, int *nsf,
435                    struct thread *td, struct ucred *cred)
436 {
437         int error;
438         u_int8_t ns;
439         u_int16_t nscnt;
440         DECLARE_RQ;
441
442         NCP_RQ_HEAD(87,td,cred);
443         ncp_rq_byte(rqp, 24);   /* Subfunction: Get Loaded Name Spaces */
444         ncp_rq_word(rqp, 0);
445         ncp_rq_byte(rqp, volume);
446         checkbad(ncp_request(conn,rqp));
447         nscnt = ncp_rp_word_lh(rqp);
448         *nsf = 0;
449         while (nscnt-- > 0) {
450                 ns = ncp_rp_byte(rqp);
451                 *nsf |= 1 << ns;
452         }
453         NCP_RQ_EXIT;
454         return error;
455 }
456
457 int
458 ncp_lookup_volume(struct ncp_conn *conn, char *volname, 
459                   u_char *volNum, u_int32_t *dirEnt,
460                   struct thread *td, struct ucred *cred)
461 {
462         int error;
463         DECLARE_RQ;
464
465         NCPNDEBUG("looking up vol %s\n", volname);
466         NCP_RQ_HEAD(87,td,cred);
467         ncp_rq_byte(rqp, 22);   /* Subfunction: Generate dir handle */
468         ncp_rq_byte(rqp, 0);    /* src name space */
469         ncp_rq_byte(rqp, 0);    /* dst name space, always zero */
470         ncp_rq_word(rqp, 0);    /* dstNSIndicator */
471
472         ncp_rq_byte(rqp, 0);    /* faked volume number */
473         ncp_rq_dword(rqp, 0);   /* faked dir_base */
474         ncp_rq_byte(rqp, 0xff); /* Don't have a dir_base */
475         ncp_rq_byte(rqp, 1);    /* 1 path component */
476         ncp_rq_pstring(rqp, volname);
477         checkbad(ncp_request(conn,rqp));
478         ncp_rp_dword_lh(rqp);   /* NSDirectoryBase*/
479         *dirEnt = ncp_rp_dword_lh(rqp);
480         *volNum = ncp_rp_byte(rqp);
481         NCP_RQ_EXIT;
482         return error;
483 }
484
485 /* 
486  * Time & date conversion routines taken from msdosfs. Although leap
487  * year calculation is bogus, it's sufficient before 2100 :)
488  */
489 /*
490  * This is the format of the contents of the deTime field in the direntry
491  * structure.
492  * We don't use bitfields because we don't know how compilers for
493  * arbitrary machines will lay them out.
494  */
495 #define DT_2SECONDS_MASK        0x1F    /* seconds divided by 2 */
496 #define DT_2SECONDS_SHIFT       0
497 #define DT_MINUTES_MASK         0x7E0   /* minutes */
498 #define DT_MINUTES_SHIFT        5
499 #define DT_HOURS_MASK           0xF800  /* hours */
500 #define DT_HOURS_SHIFT          11
501
502 /*
503  * This is the format of the contents of the deDate field in the direntry
504  * structure.
505  */
506 #define DD_DAY_MASK             0x1F    /* day of month */
507 #define DD_DAY_SHIFT            0
508 #define DD_MONTH_MASK           0x1E0   /* month */
509 #define DD_MONTH_SHIFT          5
510 #define DD_YEAR_MASK            0xFE00  /* year - 1980 */
511 #define DD_YEAR_SHIFT           9
512 /*
513  * Total number of days that have passed for each month in a regular year.
514  */
515 static u_short regyear[] = {
516         31, 59, 90, 120, 151, 181,
517         212, 243, 273, 304, 334, 365
518 };
519
520 /*
521  * Total number of days that have passed for each month in a leap year.
522  */
523 static u_short leapyear[] = {
524         31, 60, 91, 121, 152, 182,
525         213, 244, 274, 305, 335, 366
526 };
527
528 /*
529  * Variables used to remember parts of the last time conversion.  Maybe we
530  * can avoid a full conversion.
531  */
532 static u_long  lasttime;
533 static u_long  lastday;
534 static u_short lastddate;
535 static u_short lastdtime;
536 /*
537  * Convert the unix version of time to dos's idea of time to be used in
538  * file timestamps. The passed in unix time is assumed to be in GMT.
539  */
540 void
541 ncp_unix2dostime(struct timespec *tsp, int tzoff, u_int16_t *ddp,
542                  u_int16_t *dtp, u_int8_t *dhp)
543 {
544         u_long t;
545         u_long days;
546         u_long inc;
547         u_long year;
548         u_long month;
549         u_short *months;
550
551         /*
552          * If the time from the last conversion is the same as now, then
553          * skip the computations and use the saved result.
554          */
555         t = tsp->tv_sec - tzoff * 60 - tz.tz_minuteswest * 60 -
556             (wall_cmos_clock ? adjkerntz : 0);
557         t &= ~1;
558         if (lasttime != t) {
559                 lasttime = t;
560                 lastdtime = (((t / 2) % 30) << DT_2SECONDS_SHIFT)
561                     + (((t / 60) % 60) << DT_MINUTES_SHIFT)
562                     + (((t / 3600) % 24) << DT_HOURS_SHIFT);
563
564                 /*
565                  * If the number of days since 1970 is the same as the last
566                  * time we did the computation then skip all this leap year
567                  * and month stuff.
568                  */
569                 days = t / (24 * 60 * 60);
570                 if (days != lastday) {
571                         lastday = days;
572                         for (year = 1970;; year++) {
573                                 inc = year & 0x03 ? 365 : 366;
574                                 if (days < inc)
575                                         break;
576                                 days -= inc;
577                         }
578                         months = year & 0x03 ? regyear : leapyear;
579                         for (month = 0; days >= months[month]; month++)
580                                 ;
581                         if (month > 0)
582                                 days -= months[month - 1];
583                         lastddate = ((days + 1) << DD_DAY_SHIFT)
584                             + ((month + 1) << DD_MONTH_SHIFT);
585                         /*
586                          * Remember dos's idea of time is relative to 1980.
587                          * unix's is relative to 1970.  If somehow we get a
588                          * time before 1980 then don't give totally crazy
589                          * results.
590                          */
591                         if (year > 1980)
592                                 lastddate += (year - 1980) << DD_YEAR_SHIFT;
593                 }
594         }
595         if (dtp)
596                 *dtp = lastdtime;
597         if (dhp)
598                 *dhp = (tsp->tv_sec & 1) * 100 + tsp->tv_nsec / 10000000;
599
600         *ddp = lastddate;
601 }
602
603 /*
604  * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that
605  * interval there were 8 regular years and 2 leap years.
606  */
607 #define SECONDSTO1980   (((8 * 365) + (2 * 366)) * (24 * 60 * 60))
608
609 static u_short lastdosdate;
610 static u_long  lastseconds;
611
612 /*
613  * Convert from dos' idea of time to unix'. This will probably only be
614  * called from the stat(), and fstat() system calls and so probably need
615  * not be too efficient.
616  */
617 void
618 ncp_dos2unixtime(u_int dd, u_int dt, u_int dh, int tzoff, struct timespec *tsp)
619 {
620         u_long seconds;
621         u_long month;
622         u_long year;
623         u_long days;
624         u_short *months;
625
626         if (dd == 0) {
627                 /*
628                  * Uninitialized field, return the epoch.
629                  */
630                 tsp->tv_sec = 0;
631                 tsp->tv_nsec = 0;
632                 return;
633         }
634         seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1)
635             + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60
636             + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600
637             + dh / 100;
638         /*
639          * If the year, month, and day from the last conversion are the
640          * same then use the saved value.
641          */
642         if (lastdosdate != dd) {
643                 lastdosdate = dd;
644                 days = 0;
645                 year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT;
646                 days = year * 365;
647                 days += year / 4 + 1;   /* add in leap days */
648                 if ((year & 0x03) == 0)
649                         days--;         /* if year is a leap year */
650                 months = year & 0x03 ? regyear : leapyear;
651                 month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT;
652                 if (month < 1 || month > 12) {
653                         month = 1;
654                 }
655                 if (month > 1)
656                         days += months[month - 2];
657                 days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1;
658                 lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980;
659         }
660         tsp->tv_sec = seconds + lastseconds + tz.tz_minuteswest * 60 +
661             tzoff * 60 + (wall_cmos_clock ? adjkerntz : 0);
662         tsp->tv_nsec = (dh % 100) * 10000000;
663 }