2 * Copyright (c) 1999, Boris Popov
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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
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 $
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>
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>
50 #include "nwfs_node.h"
51 #include "nwfs_subr.h"
53 MALLOC_DEFINE(M_NWFSDATA
, "NWFS data", "NWFS private data");
56 ncp_extract_file_info(struct nwmount
*nmp
, struct ncp_rq
*rqp
,
57 struct nw_entry_info
*target
)
60 const int info_struct_size
= sizeof(struct nw_entry_info
) - 257;
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
);
72 ncp_update_file_info(struct nwmount
*nmp
, struct ncp_rq
*rqp
,
73 struct nw_entry_info
*target
)
75 int info_struct_size
= sizeof(struct nw_entry_info
) - 257;
77 ncp_rp_mem(rqp
,(caddr_t
)target
,info_struct_size
);
82 ncp_initsearch(struct vnode
*dvp
, struct thread
*td
, struct ucred
*cred
)
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
;
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
));
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
)
110 struct ncp_conn
*conn
= NWFSTOCONN(nmp
);
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
);
133 * Returns information for a (one-component) name relative to the specified
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
)
141 struct ncp_conn
*conn
=NWFSTOCONN(nmp
);
143 u_char volnum
= nmp
->n_volume
, ns
;
146 if (target
== NULL
) {
147 NCPFATAL("target == NULL\n");
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
));
160 ncp_extract_file_info(nmp
, rqp
, target
);
162 ncp_update_file_info(nmp
, rqp
, target
);
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 ...
172 ncp_lookup(struct vnode
*dvp
, int len
, char *name
, struct nw_entry_info
*fap
,
173 struct thread
*td
, struct ucred
*cred
)
176 struct nwnode
*dnp
= VTONW(dvp
);
177 struct ncp_conn
*conn
;
180 if (!dvp
|| dvp
->v_type
!= VDIR
) {
181 nwfs_printf("dvp is NULL or not a directory.\n");
185 conn
= NWFSTOCONN(nmp
);
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
,
192 error
= ncp_obtain_info(nmp
, dnp
->n_fid
.f_parent
,
193 dnp
->n_nmlen
, dnp
->n_name
, fap
, td
, cred
);
196 } else if (len
== 2 && name
[0] == '.' && name
[1] == '.') {
197 kprintf("%s: knows NOTHING about '..'\n", __func__
);
200 error
= ncp_obtain_info(nmp
, dnp
->n_fid
.f_id
,
201 len
, name
, fap
, td
, cred
);
206 static void ConvertToNWfromDWORD(u_int32_t sfd
, ncp_fh
*fh
);
208 ConvertToNWfromDWORD(u_int32_t sfd
, ncp_fh
*fh
) {
209 fh
->val1
= (fh
->val
.val32
= sfd
);
214 * If both dir and name are NULL, then in target there's already a looked-up
215 * entry that wants to be opened.
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
)
226 struct ncp_conn
*conn
=NWFSTOCONN(nmp
);
227 u_int16_t search_attribs
= SA_ALL
& (~SA_SUBDIR_FILES
);
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
;
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
);
246 * The desired acc rights seem to be the inherited rights mask for
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
));
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
);
260 case NWE_FILE_NO_CREATE_PRIV
:
268 ncp_close_file(struct ncp_conn
*conn
, ncp_fh
*fh
, struct thread
*td
,
274 NCP_RQ_HEAD(66,td
,cred
);
276 ncp_rq_mem(rqp
, (caddr_t
)fh
, 6);
277 error
= ncp_request(conn
,rqp
);
283 ncp_DeleteNSEntry(struct nwmount
*nmp
, u_int32_t dirent
, int namelen
,
284 char *name
, struct thread
*td
, struct ucred
*cred
)
287 struct ncp_conn
*conn
=NWFSTOCONN(nmp
);
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
);
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
)
310 NCP_RQ_HEAD(87,td
,cred
);
312 ncp_rq_byte(rqp
, ns
);
314 ncp_rq_word(rqp
, oldtype
);
315 /* source Handle Path */
316 ncp_rq_byte(rqp
, volume
);
317 ncp_rq_dword(rqp
, fdir
);
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
);
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
);
333 ncp_modify_file_or_subdir_dos_info(struct nwmount
*nmp
, struct vnode
*vp
,
335 struct nw_modify_dos_info
*info
,
336 struct thread
*td
, struct ucred
*cred
)
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
);
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
);
359 ncp_setattr(struct vnode
*vp
, struct vattr
*vap
, struct ucred
*cred
,
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
;
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
);
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
;
383 ncp_close_file(conn
, &nwn
.fh
, td
, cred
);
384 if (error
) return error
;
387 bzero(&info
, sizeof(info
));
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
);
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
);
398 error
= ncp_modify_file_or_subdir_dos_info(nmp
, vp
, info_mask
, &info
,td
,cred
);
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
)
411 NCP_RQ_HEAD_S(22,44,td
,cred
);
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
;
427 ncp_rp_mem(rqp
,(caddr_t
)&target
->volume_name
, len
);
434 ncp_get_namespaces(struct ncp_conn
*conn
, u_int32_t volume
, int *nsf
,
435 struct thread
*td
, struct ucred
*cred
)
442 NCP_RQ_HEAD(87,td
,cred
);
443 ncp_rq_byte(rqp
, 24); /* Subfunction: Get Loaded Name Spaces */
445 ncp_rq_byte(rqp
, volume
);
446 checkbad(ncp_request(conn
,rqp
));
447 nscnt
= ncp_rp_word_lh(rqp
);
449 while (nscnt
-- > 0) {
450 ns
= ncp_rp_byte(rqp
);
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
)
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 */
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
);
486 * Time & date conversion routines taken from msdosfs. Although leap
487 * year calculation is bogus, it's sufficient before 2100 :)
490 * This is the format of the contents of the deTime field in the direntry
492 * We don't use bitfields because we don't know how compilers for
493 * arbitrary machines will lay them out.
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
503 * This is the format of the contents of the deDate field in the direntry
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
513 * Total number of days that have passed for each month in a regular year.
515 static u_short regyear
[] = {
516 31, 59, 90, 120, 151, 181,
517 212, 243, 273, 304, 334, 365
521 * Total number of days that have passed for each month in a leap year.
523 static u_short leapyear
[] = {
524 31, 60, 91, 121, 152, 182,
525 213, 244, 274, 305, 335, 366
529 * Variables used to remember parts of the last time conversion. Maybe we
530 * can avoid a full conversion.
532 static u_long lasttime
;
533 static u_long lastday
;
534 static u_short lastddate
;
535 static u_short lastdtime
;
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.
541 ncp_unix2dostime(struct timespec
*tsp
, int tzoff
, u_int16_t
*ddp
,
542 u_int16_t
*dtp
, u_int8_t
*dhp
)
552 * If the time from the last conversion is the same as now, then
553 * skip the computations and use the saved result.
555 t
= tsp
->tv_sec
- tzoff
* 60 - tz
.tz_minuteswest
* 60 -
556 (wall_cmos_clock
? adjkerntz
: 0);
560 lastdtime
= (((t
/ 2) % 30) << DT_2SECONDS_SHIFT
)
561 + (((t
/ 60) % 60) << DT_MINUTES_SHIFT
)
562 + (((t
/ 3600) % 24) << DT_HOURS_SHIFT
);
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
569 days
= t
/ (24 * 60 * 60);
570 if (days
!= lastday
) {
572 for (year
= 1970;; year
++) {
573 inc
= year
& 0x03 ? 365 : 366;
578 months
= year
& 0x03 ? regyear
: leapyear
;
579 for (month
= 0; days
>= months
[month
]; month
++)
582 days
-= months
[month
- 1];
583 lastddate
= ((days
+ 1) << DD_DAY_SHIFT
)
584 + ((month
+ 1) << DD_MONTH_SHIFT
);
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
592 lastddate
+= (year
- 1980) << DD_YEAR_SHIFT
;
598 *dhp
= (tsp
->tv_sec
& 1) * 100 + tsp
->tv_nsec
/ 10000000;
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.
607 #define SECONDSTO1980 (((8 * 365) + (2 * 366)) * (24 * 60 * 60))
609 static u_short lastdosdate
;
610 static u_long lastseconds
;
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.
618 ncp_dos2unixtime(u_int dd
, u_int dt
, u_int dh
, int tzoff
, struct timespec
*tsp
)
628 * Uninitialized field, return the epoch.
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
639 * If the year, month, and day from the last conversion are the
640 * same then use the saved value.
642 if (lastdosdate
!= dd
) {
645 year
= (dd
& DD_YEAR_MASK
) >> DD_YEAR_SHIFT
;
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) {
656 days
+= months
[month
- 2];
657 days
+= ((dd
& DD_DAY_MASK
) >> DD_DAY_SHIFT
) - 1;
658 lastseconds
= (days
* 24 * 60 * 60) + SECONDSTO1980
;
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;