e05b217a14151c26212232ff1410d13a4dec3204
[kai/samba.git] / source3 / smbd / vfs.c
1 /*
2    Unix SMB/Netbios implementation.
3    Version 3.0
4    VFS initialisation and support functions
5    Copyright (C) Tim Potter 1999
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "includes.h"
23
24 /* Some structures to help us initialise the vfs operations table */
25
26 struct vfs_syminfo {
27         char *name;
28         void *fptr;
29 };
30
31 /* Default vfs hooks.  WARNING: The order of these initialisers is
32    very important.  They must be in the same order as defined in
33    vfs.h.  Change at your own peril. */
34
35 static struct vfs_ops default_vfs_ops = {
36
37         /* Disk operations */
38
39         vfswrap_dummy_connect,
40         vfswrap_dummy_disconnect,
41         vfswrap_disk_free,
42
43         /* Directory operations */
44
45         vfswrap_opendir,
46         vfswrap_readdir,
47         vfswrap_mkdir,
48         vfswrap_rmdir,
49         vfswrap_closedir,
50
51         /* File operations */
52
53         vfswrap_open,
54         vfswrap_close,
55         vfswrap_read,
56         vfswrap_write,
57         vfswrap_lseek,
58         vfswrap_rename,
59         vfswrap_fsync,
60         vfswrap_stat,
61         vfswrap_fstat,
62         vfswrap_lstat,
63         vfswrap_unlink,
64         vfswrap_chmod,
65         vfswrap_fchmod,
66         vfswrap_chown,
67         vfswrap_fchown,
68         vfswrap_chdir,
69         vfswrap_getwd,
70         vfswrap_utime,
71         vfswrap_ftruncate,
72         vfswrap_lock,
73         vfswrap_symlink,
74         vfswrap_readlink,
75         vfswrap_link,
76         vfswrap_mknod,
77
78         vfswrap_fget_nt_acl,
79         vfswrap_get_nt_acl,
80         vfswrap_fset_nt_acl,
81         vfswrap_set_nt_acl,
82
83 #if defined(HAVE_NO_ACLS)
84         NULL,
85         NULL
86 #else
87         vfswrap_chmod_acl,
88         vfswrap_fchmod_acl
89 #endif
90 };
91
92 /****************************************************************************
93   initialise default vfs hooks
94 ****************************************************************************/
95
96 static BOOL vfs_init_default(connection_struct *conn)
97 {
98     DEBUG(3, ("Initialising default vfs hooks\n"));
99
100     memcpy(&conn->vfs_ops, &default_vfs_ops, sizeof(struct vfs_ops));
101     return True;
102 }
103
104 /****************************************************************************
105   initialise custom vfs hooks
106 ****************************************************************************/
107
108 #ifdef HAVE_LIBDL
109 static BOOL vfs_init_custom(connection_struct *conn)
110 {
111         int vfs_version = -1;
112         struct vfs_ops *ops, *(*init_fptr)(int *, struct vfs_ops *);
113
114         DEBUG(3, ("Initialising custom vfs hooks from %s\n",
115                   lp_vfsobj(SNUM(conn))));
116
117         /* Open object file */
118         if ((conn->dl_handle = sys_dlopen(lp_vfsobj(SNUM(conn)), 
119                                           RTLD_NOW | RTLD_GLOBAL)) == NULL) {
120                 DEBUG(0, ("Error opening %s: %s\n", lp_vfsobj(SNUM(conn)), sys_dlerror()));
121                 return False;
122         }
123
124         /* Get handle on vfs_init() symbol */
125         init_fptr = (struct vfs_ops *(*)(int *, struct vfs_ops *))sys_dlsym(conn->dl_handle, "vfs_init");
126
127         if (init_fptr == NULL) {
128                 DEBUG(0, ("No vfs_init() symbol found in %s\n",
129                           lp_vfsobj(SNUM(conn))));
130                 return False;
131         }
132
133         /* Initialise vfs_ops structure */
134         conn->vfs_ops = default_vfs_ops;
135
136         if ((ops = init_fptr(&vfs_version, &conn->vfs_ops)) == NULL) {
137                 DEBUG(0, ("vfs_init function from %s failed\n", lp_vfsobj(SNUM(conn))));
138                 return False;
139         }
140         
141         if (vfs_version != SMB_VFS_INTERFACE_VERSION) {
142                 DEBUG(0, ("vfs_init returned wrong interface version info (was %d, should be %d)\n",
143                           vfs_version, SMB_VFS_INTERFACE_VERSION ));
144                 return False;
145         }
146         
147         if (ops != &conn->vfs_ops) {
148                 memcpy(&conn->vfs_ops, ops, sizeof(struct vfs_ops));
149         }
150
151         return True;
152 }
153 #endif
154
155 /*****************************************************************
156  Generic VFS init.
157 ******************************************************************/
158
159 BOOL smbd_vfs_init(connection_struct *conn)
160 {
161         if (*lp_vfsobj(SNUM(conn))) {
162 #ifdef HAVE_LIBDL
163  
164                 /* Loadable object file */
165  
166                 if (!vfs_init_custom(conn)) {
167                         DEBUG(0, ("smbd_vfs_init: vfs_init_custom failed\n"));
168                         return False;
169                 }
170
171                 return True;
172 #else
173                 DEBUG(0, ("smbd_vfs_init: No libdl present - cannot use VFS objects\n"));
174                 return False;
175 #endif
176         }
177  
178         /* Normal share - initialise with disk access functions */
179  
180         return vfs_init_default(conn);
181 }
182
183 /*******************************************************************
184  Check if directory exists.
185 ********************************************************************/
186
187 BOOL vfs_directory_exist(connection_struct *conn, const char *dname, SMB_STRUCT_STAT *st)
188 {
189         SMB_STRUCT_STAT st2;
190         BOOL ret;
191
192         if (!st)
193                 st = &st2;
194
195         if (vfs_stat(conn,dname,st) != 0)
196                 return(False);
197
198         ret = S_ISDIR(st->st_mode);
199         if(!ret)
200                 errno = ENOTDIR;
201
202         return ret;
203 }
204
205 /*******************************************************************
206  vfs getwd wrapper 
207 ********************************************************************/
208 char *vfs_getwd(connection_struct *conn, char *path)
209 {
210         return conn->vfs_ops.getwd(conn,path);
211 }
212
213 /*******************************************************************
214  vfs mkdir wrapper 
215 ********************************************************************/
216
217 int vfs_mkdir(connection_struct *conn, const char *name, mode_t mode)
218 {
219         int ret;
220         SMB_STRUCT_STAT sbuf;
221
222         if(!(ret=conn->vfs_ops.mkdir(conn,name,mode))) {
223                 /*
224                  * Check if high bits should have been set,
225                  * then (if bits are missing): add them.
226                  * Consider bits automagically set by UNIX, i.e. SGID bit from parent dir.
227                  */
228                 if(mode & ~(S_IRWXU|S_IRWXG|S_IRWXO) &&
229                                 !vfs_stat(conn,name,&sbuf) && (mode & ~sbuf.st_mode))
230                         vfs_chmod(conn,name,sbuf.st_mode | (mode & ~sbuf.st_mode));
231         }
232         return ret;
233 }
234
235 /*******************************************************************
236  Check if an object exists in the vfs.
237 ********************************************************************/
238
239 BOOL vfs_object_exist(connection_struct *conn,const char *fname,SMB_STRUCT_STAT *sbuf)
240 {
241         SMB_STRUCT_STAT st;
242
243         if (!sbuf)
244                 sbuf = &st;
245
246         ZERO_STRUCTP(sbuf);
247
248         if (vfs_stat(conn,fname,sbuf) == -1)
249                 return(False);
250         return True;
251 }
252
253 /*******************************************************************
254  Check if a file exists in the vfs.
255 ********************************************************************/
256
257 BOOL vfs_file_exist(connection_struct *conn,char *fname,SMB_STRUCT_STAT *sbuf)
258 {
259         SMB_STRUCT_STAT st;
260
261         if (!sbuf)
262                 sbuf = &st;
263
264         ZERO_STRUCTP(sbuf);
265
266         if (vfs_stat(conn,fname,sbuf) == -1)
267                 return False;
268         return(S_ISREG(sbuf->st_mode));
269 }
270
271 /****************************************************************************
272  Read data from fsp on the vfs. (note: EINTR re-read differs from vfs_write_data)
273 ****************************************************************************/
274
275 ssize_t vfs_read_data(files_struct *fsp, char *buf, size_t byte_count)
276 {
277         size_t total=0;
278
279         while (total < byte_count)
280         {
281                 ssize_t ret = fsp->conn->vfs_ops.read(fsp, fsp->fd, buf + total,
282                                                                                           byte_count - total);
283
284                 if (ret == 0) return total;
285                 if (ret == -1) {
286                         if (errno == EINTR)
287                                 continue;
288                         else
289                                 return -1;
290                 }
291                 total += ret;
292         }
293         return (ssize_t)total;
294 }
295
296 /****************************************************************************
297  Write data to a fd on the vfs.
298 ****************************************************************************/
299
300 ssize_t vfs_write_data(files_struct *fsp,const char *buffer,size_t N)
301 {
302         size_t total=0;
303         ssize_t ret;
304
305         while (total < N) {
306                 ret = fsp->conn->vfs_ops.write(fsp,fsp->fd,buffer + total,N - total);
307
308                 if (ret == -1)
309                         return -1;
310                 if (ret == 0)
311                         return total;
312
313                 total += ret;
314         }
315         return (ssize_t)total;
316 }
317
318 /****************************************************************************
319  An allocate file space call using the vfs interface.
320  Allocates space for a file from a filedescriptor.
321  Returns 0 on success, -1 on failure.
322 ****************************************************************************/
323
324 int vfs_allocate_file_space(files_struct *fsp, SMB_OFF_T len)
325 {
326         int ret;
327         SMB_STRUCT_STAT st;
328         connection_struct *conn = fsp->conn;
329         struct vfs_ops *vfs_ops = &conn->vfs_ops;
330         SMB_OFF_T space_avail;
331         SMB_BIG_UINT bsize,dfree,dsize;
332
333         release_level_2_oplocks_on_change(fsp);
334
335         /*
336          * Actually try and commit the space on disk....
337          */
338
339         DEBUG(10,("vfs_allocate_file_space: file %s, len %.0f\n", fsp->fsp_name, (double)len ));
340
341         ret = vfs_fstat(fsp,fsp->fd,&st);
342         if (ret == -1)
343                 return ret;
344
345         if (len == st.st_size)
346                 return 0;
347
348         if (len < st.st_size) {
349                 /* Shrink - use ftruncate. */
350
351                 DEBUG(10,("vfs_allocate_file_space: file %s, shrink. Current size %.0f\n",
352                                 fsp->fsp_name, (double)st.st_size ));
353
354                 flush_write_cache(fsp, SIZECHANGE_FLUSH);
355                 if ((ret = vfs_ops->ftruncate(fsp, fsp->fd, len)) != -1) {
356                         set_filelen_write_cache(fsp, len);
357                 }
358                 return ret;
359         }
360
361         /* Grow - we need to test if we have enough space. */
362
363         if (!lp_strict_allocate(SNUM(fsp->conn)))
364                 return 0;
365
366         len -= st.st_size;
367         len /= 1024; /* Len is now number of 1k blocks needed. */
368         space_avail = (SMB_OFF_T)conn->vfs_ops.disk_free(conn,fsp->fsp_name,False,&bsize,&dfree,&dsize);
369
370         DEBUG(10,("vfs_allocate_file_space: file %s, grow. Current size %.0f, needed blocks = %lu, space avail = %lu\n",
371                         fsp->fsp_name, (double)st.st_size, (unsigned long)len, (unsigned long)space_avail ));
372
373         if (len > space_avail) {
374                 errno = ENOSPC;
375                 return -1;
376         }
377
378         return 0;
379 }
380
381 /****************************************************************************
382  A vfs set_filelen call.
383  set the length of a file from a filedescriptor.
384  Returns 0 on success, -1 on failure.
385 ****************************************************************************/
386
387 int vfs_set_filelen(files_struct *fsp, SMB_OFF_T len)
388 {
389         int ret;
390
391         release_level_2_oplocks_on_change(fsp);
392         DEBUG(10,("vfs_set_filelen: ftruncate %s to len %.0f\n", fsp->fsp_name, (double)len));
393         flush_write_cache(fsp, SIZECHANGE_FLUSH);
394         if ((ret = fsp->conn->vfs_ops.ftruncate(fsp, fsp->fd, len)) != -1)
395                 set_filelen_write_cache(fsp, len);
396
397         return ret;
398 }
399
400 /****************************************************************************
401  Transfer some data (n bytes) between two file_struct's.
402 ****************************************************************************/
403
404 static files_struct *in_fsp;
405 static files_struct *out_fsp;
406
407 static ssize_t read_fn(int fd, void *buf, size_t len)
408 {
409         return in_fsp->conn->vfs_ops.read(in_fsp, fd, buf, len);
410 }
411
412 static ssize_t write_fn(int fd, const void *buf, size_t len)
413 {
414         return out_fsp->conn->vfs_ops.write(out_fsp, fd, buf, len);
415 }
416
417 SMB_OFF_T vfs_transfer_file(files_struct *in, files_struct *out, SMB_OFF_T n)
418 {
419         in_fsp = in;
420         out_fsp = out;
421
422         return transfer_file_internal(in_fsp->fd, out_fsp->fd, n, read_fn, write_fn);
423 }
424
425 /*******************************************************************
426  A vfs_readdir wrapper which just returns the file name.
427 ********************************************************************/
428
429 char *vfs_readdirname(connection_struct *conn, void *p)
430 {
431         struct dirent *ptr;
432         char *dname;
433
434         if (!p)
435                 return(NULL);
436
437         ptr = (struct dirent *)conn->vfs_ops.readdir(conn,p);
438         if (!ptr)
439                 return(NULL);
440
441         dname = ptr->d_name;
442
443 #ifdef NEXT2
444         if (telldir(p) < 0)
445                 return(NULL);
446 #endif
447
448 #ifdef HAVE_BROKEN_READDIR
449         /* using /usr/ucb/cc is BAD */
450         dname = dname - 2;
451 #endif
452
453         return(dname);
454 }
455
456 /* VFS options not quite working yet */
457
458 #if 0
459
460 /***************************************************************************
461   handle the interpretation of the vfs option parameter
462  *************************************************************************/
463 static BOOL handle_vfs_option(char *pszParmValue, char **ptr)
464 {
465     struct vfs_options *new_option, **options = (struct vfs_options **)ptr;
466     int i;
467
468     /* Create new vfs option */
469
470     new_option = (struct vfs_options *)malloc(sizeof(*new_option));
471     if (new_option == NULL) {
472         return False;
473     }
474
475     ZERO_STRUCTP(new_option);
476
477     /* Get name and value */
478
479     new_option->name = strtok(pszParmValue, "=");
480
481     if (new_option->name == NULL) {
482         return False;
483     }
484
485     while(isspace(*new_option->name)) {
486         new_option->name++;
487     }
488
489     for (i = strlen(new_option->name); i > 0; i--) {
490         if (!isspace(new_option->name[i - 1])) break;
491     }
492
493     new_option->name[i] = '\0';
494     new_option->name = strdup(new_option->name);
495
496     new_option->value = strtok(NULL, "=");
497
498     if (new_option->value != NULL) {
499
500         while(isspace(*new_option->value)) {
501             new_option->value++;
502         }
503         
504         for (i = strlen(new_option->value); i > 0; i--) {
505             if (!isspace(new_option->value[i - 1])) break;
506         }
507         
508         new_option->value[i] = '\0';
509         new_option->value = strdup(new_option->value);
510     }
511
512     /* Add to list */
513
514     DLIST_ADD(*options, new_option);
515
516     return True;
517 }
518
519 #endif
520
521
522 /*******************************************************************
523  A wrapper for vfs_chdir().
524 ********************************************************************/
525
526 int vfs_ChDir(connection_struct *conn, char *path)
527 {
528         int res;
529         static pstring LastDir="";
530
531         if (strcsequal(path,"."))
532                 return(0);
533
534         if (*path == '/' && strcsequal(LastDir,path))
535                 return(0);
536
537         DEBUG(3,("vfs_ChDir to %s\n",path));
538
539         res = vfs_chdir(conn,path);
540         if (!res)
541                 pstrcpy(LastDir,path);
542         return(res);
543 }
544
545 /* number of list structures for a caching GetWd function. */
546 #define MAX_GETWDCACHE (50)
547
548 struct
549 {
550   SMB_DEV_T dev; /* These *must* be compatible with the types returned in a stat() call. */
551   SMB_INO_T inode; /* These *must* be compatible with the types returned in a stat() call. */
552   char *dos_path; /* The pathname in DOS format. */
553   BOOL valid;
554 } ino_list[MAX_GETWDCACHE];
555
556 extern BOOL use_getwd_cache;
557
558 /****************************************************************************
559  Prompte a ptr (to make it recently used)
560 ****************************************************************************/
561
562 static void array_promote(char *array,int elsize,int element)
563 {
564         char *p;
565         if (element == 0)
566                 return;
567
568         p = (char *)malloc(elsize);
569
570         if (!p) {
571                 DEBUG(5,("array_promote: malloc fail\n"));
572                 return;
573         }
574
575         memcpy(p,array + element * elsize, elsize);
576         memmove(array + elsize,array,elsize*element);
577         memcpy(array,p,elsize);
578         SAFE_FREE(p);
579 }
580
581 /*******************************************************************
582  Return the absolute current directory path - given a UNIX pathname.
583  Note that this path is returned in DOS format, not UNIX
584  format. Note this can be called with conn == NULL.
585 ********************************************************************/
586
587 char *vfs_GetWd(connection_struct *conn, char *path)
588 {
589   pstring s;
590   static BOOL getwd_cache_init = False;
591   SMB_STRUCT_STAT st, st2;
592   int i;
593
594   *s = 0;
595
596   if (!use_getwd_cache)
597     return(vfs_getwd(conn,path));
598
599   /* init the cache */
600   if (!getwd_cache_init)
601   {
602     getwd_cache_init = True;
603     for (i=0;i<MAX_GETWDCACHE;i++)
604     {
605       string_set(&ino_list[i].dos_path,"");
606       ino_list[i].valid = False;
607     }
608   }
609
610   /*  Get the inode of the current directory, if this doesn't work we're
611       in trouble :-) */
612
613   if (vfs_stat(conn, ".",&st) == -1)
614   {
615     DEBUG(0,("Very strange, couldn't stat \".\" path=%s\n", path));
616     return(vfs_getwd(conn,path));
617   }
618
619
620   for (i=0; i<MAX_GETWDCACHE; i++)
621     if (ino_list[i].valid)
622     {
623
624       /*  If we have found an entry with a matching inode and dev number
625           then find the inode number for the directory in the cached string.
626           If this agrees with that returned by the stat for the current
627           directory then all is o.k. (but make sure it is a directory all
628           the same...) */
629
630       if (st.st_ino == ino_list[i].inode &&
631           st.st_dev == ino_list[i].dev)
632       {
633         if (vfs_stat(conn,ino_list[i].dos_path,&st2) == 0)
634         {
635           if (st.st_ino == st2.st_ino &&
636               st.st_dev == st2.st_dev &&
637               (st2.st_mode & S_IFMT) == S_IFDIR)
638           {
639             pstrcpy (path, ino_list[i].dos_path);
640
641             /* promote it for future use */
642             array_promote((char *)&ino_list[0],sizeof(ino_list[0]),i);
643             return (path);
644           }
645           else
646           {
647             /*  If the inode is different then something's changed,
648                 scrub the entry and start from scratch. */
649             ino_list[i].valid = False;
650           }
651         }
652       }
653     }
654
655
656   /*  We don't have the information to hand so rely on traditional methods.
657       The very slow getcwd, which spawns a process on some systems, or the
658       not quite so bad getwd. */
659
660   if (!vfs_getwd(conn,s))
661   {
662     DEBUG(0,("vfs_GetWd: vfs_getwd call failed, errno %s\n",strerror(errno)));
663     return (NULL);
664   }
665
666   pstrcpy(path,s);
667
668   DEBUG(5,("vfs_GetWd %s, inode %.0f, dev %.0f\n",s,(double)st.st_ino,(double)st.st_dev));
669
670   /* add it to the cache */
671   i = MAX_GETWDCACHE - 1;
672   string_set(&ino_list[i].dos_path,s);
673   ino_list[i].dev = st.st_dev;
674   ino_list[i].inode = st.st_ino;
675   ino_list[i].valid = True;
676
677   /* put it at the top of the list */
678   array_promote((char *)&ino_list[0],sizeof(ino_list[0]),i);
679
680   return (path);
681 }
682
683 /*******************************************************************
684  Reduce a file name, removing .. elements and checking that
685  it is below dir in the heirachy. This uses vfs_GetWd() and so must be run
686  on the system that has the referenced file system.
687  Widelinks are allowed if widelinks is true.
688 ********************************************************************/
689
690 BOOL reduce_name(connection_struct *conn, char *s,char *dir,BOOL widelinks)
691 {
692 #ifndef REDUCE_PATHS
693   return True;
694 #else
695   pstring dir2;
696   pstring wd;
697   pstring base_name;
698   pstring newname;
699   char *p=NULL;
700   BOOL relative = (*s != '/');
701
702   *dir2 = *wd = *base_name = *newname = 0;
703
704   if (widelinks)
705   {
706     unix_clean_name(s);
707     /* can't have a leading .. */
708     if (strncmp(s,"..",2) == 0 && (s[2]==0 || s[2]=='/'))
709     {
710       DEBUG(3,("Illegal file name? (%s)\n",s));
711       return(False);
712     }
713
714     if (strlen(s) == 0)
715       pstrcpy(s,"./");
716
717     return(True);
718   }
719
720   DEBUG(3,("reduce_name [%s] [%s]\n",s,dir));
721
722   /* remove any double slashes */
723   all_string_sub(s,"//","/",0);
724
725   pstrcpy(base_name,s);
726   p = strrchr_m(base_name,'/');
727
728   if (!p)
729     return(True);
730
731   if (!vfs_GetWd(conn,wd))
732   {
733     DEBUG(0,("couldn't vfs_GetWd for %s %s\n",s,dir));
734     return(False);
735   }
736
737   if (vfs_ChDir(conn,dir) != 0)
738   {
739     DEBUG(0,("couldn't vfs_ChDir to %s\n",dir));
740     return(False);
741   }
742
743   if (!vfs_GetWd(conn,dir2))
744   {
745     DEBUG(0,("couldn't vfs_GetWd for %s\n",dir));
746     vfs_ChDir(conn,wd);
747     return(False);
748   }
749
750   if (p && (p != base_name))
751   {
752     *p = 0;
753     if (strcmp(p+1,".")==0)
754       p[1]=0;
755     if (strcmp(p+1,"..")==0)
756       *p = '/';
757   }
758
759   if (vfs_ChDir(conn,base_name) != 0)
760   {
761     vfs_ChDir(conn,wd);
762     DEBUG(3,("couldn't vfs_ChDir for %s %s basename=%s\n",s,dir,base_name));
763     return(False);
764   }
765
766   if (!vfs_GetWd(conn,newname))
767   {
768     vfs_ChDir(conn,wd);
769     DEBUG(2,("couldn't get vfs_GetWd for %s %s\n",s,dir2));
770     return(False);
771   }
772
773   if (p && (p != base_name))
774   {
775     pstrcat(newname,"/");
776     pstrcat(newname,p+1);
777   }
778
779   {
780     size_t l = strlen(dir2);
781     if (dir2[l-1] == '/')
782       l--;
783
784     if (strncmp(newname,dir2,l) != 0)
785     {
786       vfs_ChDir(conn,wd);
787       DEBUG(2,("Bad access attempt? s=%s dir=%s newname=%s l=%d\n",s,dir2,newname,(int)l));
788       return(False);
789     }
790
791     if (relative)
792     {
793       if (newname[l] == '/')
794         pstrcpy(s,newname + l + 1);
795       else
796         pstrcpy(s,newname+l);
797     }
798     else
799       pstrcpy(s,newname);
800   }
801
802   vfs_ChDir(conn,wd);
803
804   if (strlen(s) == 0)
805     pstrcpy(s,"./");
806
807   DEBUG(3,("reduced to %s\n",s));
808   return(True);
809 #endif
810 }