]> CyberLeo.Net >> Repos - FreeBSD/releng/7.2.git/blob - sys/fs/nwfs/nwfs_subr.c
Create releng/7.2 from stable/7 in preparation for 7.2-RELEASE.
[FreeBSD/releng/7.2.git] / sys / fs / nwfs / nwfs_subr.c
1 /*-
2  * Copyright (c) 1999, 2001 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$
33  */
34 #include <sys/param.h>
35 #include <sys/systm.h>
36 #include <sys/kernel.h>
37 #include <sys/clock.h>
38 #include <sys/lock.h>
39 #include <sys/lockmgr.h>
40 #include <sys/malloc.h>
41 #include <sys/clock.h>
42 #include <sys/time.h>
43
44 #include <netncp/ncp.h>
45 #include <netncp/ncp_conn.h>
46 #include <netncp/ncp_ncp.h>
47 #include <netncp/ncp_subr.h>
48 #include <netncp/ncp_rq.h>
49 #include <netncp/nwerror.h>
50
51 #include <fs/nwfs/nwfs.h>
52 #include <fs/nwfs/nwfs_node.h>
53 #include <fs/nwfs/nwfs_subr.h>
54
55 #define NCP_INFOSZ      (sizeof(struct nw_entry_info) - 257)
56
57 MALLOC_DEFINE(M_NWFSDATA, "nwfs_data", "NWFS private data");
58
59 static int
60 ncp_extract_file_info(struct nwmount *nmp, struct ncp_rq *rqp,
61         struct nw_entry_info *target, int withname)
62 {
63         u_int8_t name_len;
64
65         md_get_mem(&rqp->rp, (caddr_t)target, NCP_INFOSZ, MB_MSYSTEM);
66         if (!withname)
67                 return 0;
68         md_get_uint8(&rqp->rp, &name_len);
69         target->nameLen = name_len;
70         md_get_mem(&rqp->rp, (caddr_t)target->entryName, name_len, MB_MSYSTEM);
71         target->entryName[name_len] = '\0';
72         ncp_path2unix(target->entryName, target->entryName, name_len, &nmp->m.nls);
73         return 0;
74 }
75
76 int
77 ncp_initsearch(struct vnode *dvp, struct thread *td, struct ucred *cred)
78 {
79         struct nwmount *nmp = VTONWFS(dvp);
80         struct ncp_conn *conn = NWFSTOCONN(nmp);
81         struct nwnode *np = VTONW(dvp);
82         struct ncp_rq *rqp;
83         u_int8_t volnum = nmp->n_volume;
84         u_int32_t dirent = np->n_fid.f_id;
85         int error;
86
87         NCPNDEBUG("vol=%d,dir=%d\n", volnum, dirent);
88         error = ncp_rq_alloc(87, conn, td, cred, &rqp);
89         if (error)
90                 return error;
91         mb_put_uint8(&rqp->rq, 2);              /* subfunction */
92         mb_put_uint8(&rqp->rq, nmp->name_space);
93         mb_put_uint8(&rqp->rq, 0);              /* reserved */
94         ncp_rq_dbase_path(rqp, volnum, dirent, 0, NULL, NULL);
95         rqp->nr_minrplen = sizeof(np->n_seq);
96         error = ncp_request(rqp);
97         if (error)
98                 return error;
99         md_get_mem(&rqp->rp, (caddr_t)&np->n_seq, sizeof(np->n_seq), MB_MSYSTEM);
100         ncp_rq_done(rqp);
101         return 0;
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         struct ncp_rq *rqp;
112         int error;
113
114         error = ncp_rq_alloc(87, conn, td, cred, &rqp);
115         if (error)
116                 return error;
117         mb_put_uint8(&rqp->rq, 3);              /* subfunction */
118         mb_put_uint8(&rqp->rq, nmp->name_space);
119         mb_put_uint8(&rqp->rq, 0);              /* data stream */
120         mb_put_uint16le(&rqp->rq, 0xffff);      /* Search attribs */
121         mb_put_uint32le(&rqp->rq, IM_ALL);      /* return info mask */
122         mb_put_mem(&rqp->rq, (caddr_t)seq, 9, MB_MSYSTEM);
123         mb_put_uint8(&rqp->rq, 2);              /* 2 byte pattern */
124         mb_put_uint8(&rqp->rq, 0xff);           /* following is a wildcard */
125         mb_put_uint8(&rqp->rq, '*');
126         rqp->nr_minrplen = sizeof(*seq) +  1 + NCP_INFOSZ + 1;
127         error = ncp_request(rqp);
128         if (error)
129                 return error;
130         md_get_mem(&rqp->rp, (caddr_t)seq, sizeof(*seq), MB_MSYSTEM);
131         md_get_uint8(&rqp->rp, NULL);           /* skip */
132         error = ncp_extract_file_info(nmp, rqp, target, 1);
133         ncp_rq_done(rqp);
134         return error;
135 }
136
137 /*
138  * Returns information for a (one-component) name relative to the specified
139  * directory.
140  */
141 int 
142 ncp_obtain_info(struct nwmount *nmp,  u_int32_t dirent,
143                 int namelen, char *path, struct nw_entry_info *target,
144                 struct thread *td,struct ucred *cred)
145 {
146         struct ncp_conn *conn=NWFSTOCONN(nmp);
147         struct ncp_rq *rqp;
148         int error;
149         u_char volnum = nmp->n_volume, ns;
150
151         if (target == NULL) {
152                 NCPFATAL("target == NULL\n");
153                 return EINVAL;
154         }
155         ns = (path == NULL || path[0] == 0) ? NW_NS_DOS : nmp->name_space;
156         error = ncp_rq_alloc(87, conn, td, cred, &rqp);
157         if (error)
158                 return error;
159         mb_put_uint8(&rqp->rq, 6);      /* subfunction */
160         mb_put_uint8(&rqp->rq, ns);
161         mb_put_uint8(&rqp->rq, ns);     /* DestNameSpace */
162         mb_put_uint16le(&rqp->rq, 0xff);        /* get all */
163         mb_put_uint32le(&rqp->rq, IM_ALL);
164         ncp_rq_dbase_path(rqp, volnum, dirent, namelen, path, &nmp->m.nls);
165         error = ncp_request(rqp);
166         if (error)
167                 return error;
168         error = ncp_extract_file_info(nmp, rqp, target, path != NULL);
169         ncp_rq_done(rqp);
170         return error;
171 }
172 /* 
173  * lookup name pointed by cnp in directory dvp and return file info in np.
174  * May be I should create a little cache, but another way is to minimize
175  * number of calls, on other hand, in multiprocess environment ...
176  */
177 int
178 ncp_lookup(struct vnode *dvp, int len, char *name, struct nw_entry_info *fap,
179                 struct thread *td,struct ucred *cred)
180 {
181         struct nwmount *nmp;
182         struct nwnode *dnp;
183         struct ncp_conn *conn;
184         int error;
185
186         if (!dvp || dvp->v_type != VDIR) {
187                 nwfs_printf("dvp is NULL or not a directory.\n");
188                 return (ENOENT);
189         }
190         dnp = VTONW(dvp);
191         nmp = VTONWFS(dvp);
192         conn = NWFSTOCONN(nmp);
193
194         if (len == 1 && name[0] == '.') {
195                 if (dnp->n_flag & NVOLUME) {
196                         error = ncp_obtain_info(nmp, dnp->n_fid.f_id, 0, NULL,
197                                 fap, td, cred);
198                 } else {
199                         error = ncp_obtain_info(nmp, dnp->n_fid.f_parent, 
200                                 dnp->n_nmlen, dnp->n_name, fap, td, cred);
201                 }
202                 return error;
203         } else if (len == 2 && name[0] == '.' && name[1] == '.') {
204                 printf("%s: knows NOTHING about '..'\n", __func__);
205                 return EIO;
206         } else {
207                 error = ncp_obtain_info(nmp, dnp->n_fid.f_id, 
208                         len, name, fap, td, cred);
209         }
210         return error;
211 }
212
213 static void ConvertToNWfromDWORD(u_int32_t sfd, ncp_fh *fh);
214 static void 
215 ConvertToNWfromDWORD(u_int32_t sfd, ncp_fh *fh) {
216         fh->val1 = (fh->val.val32 = sfd);
217         return;
218 }
219
220 /*
221  * If both dir and name are NULL, then in target there's already a looked-up
222  * entry that wants to be opened.
223  */
224 int 
225 ncp_open_create_file_or_subdir(struct nwmount *nmp,struct vnode *dvp,int namelen,
226             char *name, int open_create_mode, u_int32_t create_attributes,
227             int desired_acc_rights, struct ncp_open_info *nop,
228             struct thread *td,struct ucred *cred)
229 {
230         
231         struct ncp_conn *conn=NWFSTOCONN(nmp);
232         struct ncp_rq *rqp;
233         u_int16_t search_attribs = SA_ALL & (~SA_SUBDIR_FILES);
234         u_int8_t volnum;
235         u_int32_t dirent;
236         int error;
237
238         error = ncp_rq_alloc(87, conn, td, cred, &rqp);
239         if (error)
240                 return error;
241         volnum = nmp->n_volume;
242         dirent = VTONW(dvp)->n_fid.f_id;
243         if ((create_attributes & aDIR) != 0) {
244                 search_attribs |= SA_SUBDIR_FILES;
245         }
246         mb_put_uint8(&rqp->rq, 1);/* subfunction */
247         mb_put_uint8(&rqp->rq, nmp->name_space);
248         mb_put_uint8(&rqp->rq, open_create_mode);
249         mb_put_uint16le(&rqp->rq, search_attribs);
250         mb_put_uint32le(&rqp->rq, IM_ALL);
251         mb_put_uint32le(&rqp->rq, create_attributes);
252         /*
253          * The desired acc rights seem to be the inherited rights mask for
254          * directories
255          */
256         mb_put_uint16le(&rqp->rq, desired_acc_rights);
257         ncp_rq_dbase_path(rqp, volnum, dirent, namelen, name, &nmp->m.nls);
258         error = ncp_request(rqp);
259         if (error) {
260                 if (error == NWE_FILE_NO_CREATE_PRIV)
261                         error = EACCES;
262                 return error;
263         }
264         md_get_uint32le(&rqp->rp, &nop->origfh);
265         md_get_uint8(&rqp->rp, &nop->action);
266         md_get_uint8(&rqp->rp, NULL);   /* skip */
267         error = ncp_extract_file_info(nmp, rqp, &nop->fattr, 1);
268         ncp_rq_done(rqp);
269         ConvertToNWfromDWORD(nop->origfh, &nop->fh);
270         return error;
271 }
272
273 int
274 ncp_close_file(struct ncp_conn *conn, ncp_fh *fh,struct thread *td,struct ucred *cred)
275 {
276         struct ncp_rq *rqp;
277         int error;
278
279         error = ncp_rq_alloc(66, conn, td, cred, &rqp);
280         if (error)
281                 return error;
282         mb_put_uint8(&rqp->rq, 0);
283         mb_put_mem(&rqp->rq, (caddr_t)fh, 6, MB_MSYSTEM);
284         error = ncp_request(rqp);
285         if (error)
286                 return error;
287         ncp_rq_done(rqp);
288         return error;
289 }
290
291 int
292 ncp_DeleteNSEntry(struct nwmount *nmp, u_int32_t dirent,
293         int namelen,char *name,struct thread *td,struct ucred *cred)
294 {
295         struct ncp_rq *rqp;
296         int error;
297         struct ncp_conn *conn=NWFSTOCONN(nmp);
298
299         error = ncp_rq_alloc(87, conn, td, cred, &rqp);
300         if (error)
301                 return error;
302         mb_put_uint8(&rqp->rq, 8);              /* subfunction */
303         mb_put_uint8(&rqp->rq, nmp->name_space);
304         mb_put_uint8(&rqp->rq, 0);              /* reserved */
305         mb_put_uint16le(&rqp->rq, SA_ALL);      /* search attribs: all */
306         ncp_rq_dbase_path(rqp, nmp->n_volume, dirent, namelen, name, &nmp->m.nls);
307         error = ncp_request(rqp);
308         if (!error)
309                 ncp_rq_done(rqp);
310         return error;
311 }
312
313 int 
314 ncp_nsrename(struct ncp_conn *conn, int volume, int ns, int oldtype, 
315         struct ncp_nlstables *nt,
316         nwdirent fdir, char *old_name, int oldlen,
317         nwdirent tdir, char *new_name, int newlen,
318         struct thread *td, struct ucred *cred)
319 {
320         struct ncp_rq *rqp;
321         int error;
322
323         error = ncp_rq_alloc(87, conn, td, cred, &rqp);
324         if (error)
325                 return error;
326         mb_put_uint8(&rqp->rq, 4);
327         mb_put_uint8(&rqp->rq, ns);
328         mb_put_uint8(&rqp->rq, 1);      /* RRenameToMySelf */
329         mb_put_uint16le(&rqp->rq, oldtype);
330         /* source Handle Path */
331         mb_put_uint8(&rqp->rq, volume);
332         mb_put_mem(&rqp->rq, (c_caddr_t)&fdir, sizeof(fdir), MB_MSYSTEM);
333         mb_put_uint8(&rqp->rq, 1);
334         mb_put_uint8(&rqp->rq, 1);      /* 1 source component */
335         /* dest Handle Path */
336         mb_put_uint8(&rqp->rq, volume);
337         mb_put_mem(&rqp->rq, (c_caddr_t)&tdir, sizeof(tdir), MB_MSYSTEM);
338         mb_put_uint8(&rqp->rq, 1);
339         mb_put_uint8(&rqp->rq, 1);      /* 1 destination component */
340         ncp_rq_pathstring(rqp, oldlen, old_name, nt);
341         ncp_rq_pathstring(rqp, newlen, new_name, nt);
342         error = ncp_request(rqp);
343         if (!error)
344                 ncp_rq_done(rqp);
345         return error;
346 }
347
348 int
349 ncp_modify_file_or_subdir_dos_info(struct nwmount *nmp, struct vnode *vp, 
350                                 u_int32_t info_mask,
351                                 struct nw_modify_dos_info *info,
352                                 struct thread *td,struct ucred *cred)
353 {
354         struct nwnode *np=VTONW(vp);
355         struct ncp_rq *rqp;
356         u_int8_t volnum = nmp->n_volume;
357         u_int32_t dirent = np->n_fid.f_id;
358         struct ncp_conn *conn=NWFSTOCONN(nmp);
359         int             error;
360
361         error = ncp_rq_alloc(87, conn, td, cred, &rqp);
362         if (error)
363                 return error;
364         mb_put_uint8(&rqp->rq, 7);      /* subfunction */
365         mb_put_uint8(&rqp->rq, nmp->name_space);
366         mb_put_uint8(&rqp->rq, 0);      /* reserved */
367         mb_put_uint16le(&rqp->rq, SA_ALL);      /* search attribs: all */
368         mb_put_uint32le(&rqp->rq, info_mask);
369         mb_put_mem(&rqp->rq, (caddr_t)info, sizeof(*info), MB_MSYSTEM);
370         ncp_rq_dbase_path(rqp, volnum, dirent, 0, NULL, NULL);
371         error = ncp_request(rqp);
372         if (!error)
373                 ncp_rq_done(rqp);
374         return error;
375 }
376
377 int
378 ncp_setattr(vp, vap, cred, td)
379         struct vnode *vp;
380         struct vattr *vap;
381         struct ucred *cred;
382         struct thread *td;
383 {
384         struct nwmount *nmp=VTONWFS(vp);
385         struct nwnode *np=VTONW(vp);
386         struct ncp_open_info nwn;
387         struct ncp_conn *conn=NWFSTOCONN(nmp);
388         struct nw_modify_dos_info info;
389         struct ncp_rq *rqp;
390         int error = 0, info_mask;
391
392         if (vap->va_size != VNOVAL) {
393                 error = ncp_open_create_file_or_subdir(nmp, vp, 0, NULL, OC_MODE_OPEN, 0,
394                                                    AR_WRITE | AR_READ, &nwn,td,cred);
395                 if (error)
396                         return error;
397                 error = ncp_rq_alloc(73, conn, td, cred, &rqp);
398                 if (error) {
399                         ncp_close_file(conn, &nwn.fh, td, cred);
400                         return error;
401                 }
402                 mb_put_uint8(&rqp->rq, 0);
403                 mb_put_mem(&rqp->rq, (caddr_t)&nwn.fh, 6, MB_MSYSTEM);
404                 mb_put_uint32be(&rqp->rq, vap->va_size);
405                 mb_put_uint16be(&rqp->rq, 0);
406                 error = ncp_request(rqp);
407                 np->n_vattr.va_size = np->n_size = vap->va_size;
408                 if (!error)
409                         ncp_rq_done(rqp);
410                 ncp_close_file(conn, &nwn.fh, td, cred);
411                 if (error)
412                         return error;
413         }
414         info_mask = 0;
415         bzero(&info, sizeof(info));
416
417         if (vap->va_mtime.tv_sec != VNOVAL) {
418                 info_mask |= (DM_MODIFY_TIME | DM_MODIFY_DATE);
419                 ncp_unix2dostime(&vap->va_mtime, nmp->m.tz, &info.modifyDate, &info.modifyTime, NULL);
420         }
421         if (vap->va_atime.tv_sec != VNOVAL) {
422                 info_mask |= (DM_LAST_ACCESS_DATE);
423                 ncp_unix2dostime(&vap->va_atime, nmp->m.tz, &info.lastAccessDate, NULL, NULL);
424         }
425         if (info_mask) {
426                 error = ncp_modify_file_or_subdir_dos_info(nmp, vp, info_mask, &info,td,cred);
427         }
428         return (error);
429 }
430
431 int
432 ncp_get_volume_info_with_number(struct ncp_conn *conn, 
433         int n, struct ncp_volume_info *target,
434         struct thread *td,struct ucred *cred)
435 {
436         struct ncp_rq *rqp;
437         u_int32_t tmp32;
438         u_int8_t len;
439         int error;
440
441         error = ncp_rq_alloc_subfn(22, 44, conn, td, cred, &rqp);
442         if (error)
443                 return error;
444         mb_put_uint8(&rqp->rq,n);
445         error = ncp_request(rqp);
446         if (error)
447                 return error;
448         md_get_uint32le(&rqp->rp, &target->total_blocks);
449         md_get_uint32le(&rqp->rp, &target->free_blocks);
450         md_get_uint32le(&rqp->rp, &target->purgeable_blocks);
451         md_get_uint32le(&rqp->rp, &target->not_yet_purgeable_blocks);
452         md_get_uint32le(&rqp->rp, &target->total_dir_entries);
453         md_get_uint32le(&rqp->rp, &target->available_dir_entries);
454         md_get_uint32le(&rqp->rp, &tmp32);
455         md_get_uint8(&rqp->rp, &target->sectors_per_block);
456         bzero(&target->volume_name, sizeof(target->volume_name));
457         md_get_uint8(&rqp->rp, &len);
458         if (len > NCP_VOLNAME_LEN) {
459                 error = ENAMETOOLONG;
460         } else {
461                 md_get_mem(&rqp->rp, (caddr_t)&target->volume_name, len, MB_MSYSTEM);
462         }
463         ncp_rq_done(rqp);
464         return error;
465 }
466
467 int
468 ncp_get_namespaces(struct ncp_conn *conn, u_int32_t volume, int *nsf,
469         struct thread *td,struct ucred *cred)
470 {
471         struct ncp_rq *rqp;
472         int error;
473         u_int8_t ns;
474         u_int16_t nscnt;
475
476         error = ncp_rq_alloc(87, conn, td, cred, &rqp);
477         if (error)
478                 return error;
479         mb_put_uint8(&rqp->rq, 24);     /* Subfunction: Get Loaded Name Spaces */
480         mb_put_uint16le(&rqp->rq, 0);   /* reserved */
481         mb_put_uint8(&rqp->rq, volume);
482         error = ncp_request(rqp);
483         if (error)
484                 return error;
485         md_get_uint16le(&rqp->rp, &nscnt);
486         *nsf = 0;
487         while (nscnt-- > 0) {
488                 md_get_uint8(&rqp->rp, &ns);
489                 *nsf |= 1 << ns;
490         }
491         ncp_rq_done(rqp);
492         return error;
493 }
494
495 int
496 ncp_lookup_volume(struct ncp_conn *conn, char *volname, 
497                 u_char *volNum, u_int32_t *dirEnt,
498                 struct thread *td,struct ucred *cred)
499 {
500         struct ncp_rq *rqp;
501         u_int32_t tmp32;
502         int error;
503
504         NCPNDEBUG("looking up vol %s\n", volname);
505         error = ncp_rq_alloc(87, conn, td, cred, &rqp);
506         if (error)
507                 return error;
508         mb_put_uint8(&rqp->rq, 22);     /* Subfunction: Generate dir handle */
509         mb_put_uint8(&rqp->rq, 0);      /* src name space */
510         mb_put_uint8(&rqp->rq, 0);      /* dst name space, always zero */
511         mb_put_uint16le(&rqp->rq, 0);   /* dstNSIndicator (Jn) */
512
513         mb_put_uint8(&rqp->rq, 0);      /* faked volume number */
514         mb_put_uint32be(&rqp->rq, 0);   /* faked dir_base */
515         mb_put_uint8(&rqp->rq, 0xff);   /* Don't have a dir_base */
516         mb_put_uint8(&rqp->rq, 1);      /* 1 path component */
517         ncp_rq_pstring(rqp, volname);
518         error = ncp_request(rqp);
519         if (error)
520                 return error;
521         md_get_uint32le(&rqp->rp, &tmp32);
522         md_get_uint32le(&rqp->rp, dirEnt);
523         md_get_uint8(&rqp->rp, volNum);
524         ncp_rq_done(rqp);
525         return error;
526 }
527
528 /*
529  * XXX: I think the timezone in struct nwfs_args is truly bogus, especially
530  * XXX: considering that nwfs_mount(8) picks this up from the kernel in
531  * XXX: the first place.  Since I can't test this, I won't attempt to fix it.
532  * XXX: /phk
533  */
534
535 void
536 ncp_unix2dostime(tsp, tzoff, ddp, dtp, dhp)
537         struct timespec *tsp;
538         int tzoff;
539         u_int16_t *ddp;
540         u_int16_t *dtp;
541         u_int8_t *dhp;
542 {
543         struct timespec t;
544
545         t = *tsp;
546         
547         t.tv_sec = - tzoff * 60 - utc_offset();
548         timespec2fattime(&t, 1, ddp, dtp, dhp);
549 }
550
551 void
552 ncp_dos2unixtime(dd, dt, dh, tzoff, tsp)
553         u_int dd;
554         u_int dt;
555         u_int dh;
556         int tzoff;
557         struct timespec *tsp;
558 {
559
560         fattime2timespec(dd, dt, dh, 1, tsp);
561         tsp->tv_sec += tzoff * 60 + utc_offset();
562 }