]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/findlib/find_one.c
kes Implement code that should properly set that a job was migrated,
[bacula/bacula] / bacula / src / findlib / find_one.c
1 /*
2
3    This file is based on GNU TAR source code. Except for a few key
4    ideas, it has been entirely rewritten for Bacula.
5
6       Kern Sibbald, MM
7
8    Thanks to the TAR programmers.
9
10      Version $Id$
11
12  */
13 /*
14    Bacula® - The Network Backup Solution
15
16    Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
17
18    The main author of Bacula is Kern Sibbald, with contributions from
19    many others, a complete list can be found in the file AUTHORS.
20    This program is Free Software; you can redistribute it and/or
21    modify it under the terms of version two of the GNU General Public
22    License as published by the Free Software Foundation plus additions
23    that are listed in the file LICENSE.
24
25    This program is distributed in the hope that it will be useful, but
26    WITHOUT ANY WARRANTY; without even the implied warranty of
27    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28    General Public License for more details.
29
30    You should have received a copy of the GNU General Public License
31    along with this program; if not, write to the Free Software
32    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
33    02110-1301, USA.
34
35    Bacula® is a registered trademark of John Walker.
36    The licensor of Bacula is the Free Software Foundation Europe
37    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
38    Switzerland, email:ftf@fsfeurope.org.
39 */
40
41 #include "bacula.h"
42 #include "find.h"
43 #ifdef HAVE_DARWIN_OS
44 #include <sys/param.h>
45 #include <sys/mount.h>
46 #include <sys/attr.h>
47 #endif
48
49 extern int32_t name_max;              /* filename max length */
50 extern int32_t path_max;              /* path name max length */
51
52 /*
53  * Structure for keeping track of hard linked files, we
54  *   keep an entry for each hardlinked file that we save,
55  *   which is the first one found. For all the other files that
56  *   are linked to this one, we save only the directory
57  *   entry so we can link it.
58  */
59 struct f_link {
60     struct f_link *next;
61     dev_t dev;                        /* device */
62     ino_t ino;                        /* inode with device is unique */
63     short linkcount;
64     uint32_t FileIndex;               /* Bacula FileIndex of this file */
65     char name[1];                     /* The name */
66 };
67
68 typedef struct f_link link_t;
69 #define LINK_HASHTABLE_BITS 16
70 #define LINK_HASHTABLE_SIZE (1<<LINK_HASHTABLE_BITS)
71 #define LINK_HASHTABLE_MASK (LINK_HASHTABLE_SIZE-1)
72
73 static inline int LINKHASH(const struct stat &info)
74 {
75     int hash = info.st_dev;
76     unsigned long long i = info.st_ino;
77     hash ^= i;
78     i >>= 16;
79     hash ^= i;
80     i >>= 16;
81     hash ^= i;
82     i >>= 16;
83     hash ^= i;
84     return hash & LINK_HASHTABLE_MASK;
85 }
86
87 static void free_dir_ff_pkt(FF_PKT *dir_ff_pkt)
88 {
89    free(dir_ff_pkt->fname);
90    free(dir_ff_pkt->link);
91    free_pool_memory(dir_ff_pkt->sys_fname);
92    free(dir_ff_pkt);
93 }
94
95 /*
96  * Check to see if we allow the file system type of a file or directory.
97  * If we do not have a list of file system types, we accept anything.
98  */
99 static int accept_fstype(FF_PKT *ff, void *dummy) {
100    int i;
101    char fs[1000];
102    bool accept = true;
103
104    if (ff->fstypes.size()) {
105       accept = false;
106       if (!fstype(ff->fname, fs, sizeof(fs))) {
107          Dmsg1(50, "Cannot determine file system type for \"%s\"\n", ff->fname);
108       } else {
109          for (i = 0; i < ff->fstypes.size(); ++i) {
110             if (strcmp(fs, (char *)ff->fstypes.get(i)) == 0) {
111                Dmsg2(100, "Accepting fstype %s for \"%s\"\n", fs, ff->fname);
112                accept = true;
113                break;
114             }
115             Dmsg3(200, "fstype %s for \"%s\" does not match %s\n", fs,
116                   ff->fname, ff->fstypes.get(i));
117          }
118       }
119    }
120    return accept;
121 }
122
123 /*
124  * Check to see if we allow the drive type of a file or directory.
125  * If we do not have a list of drive types, we accept anything.
126  */
127 static int accept_drivetype(FF_PKT *ff, void *dummy) {
128    int i;
129    char dt[100];
130    bool accept = true;
131
132    if (ff->drivetypes.size()) {
133       accept = false;
134       if (!drivetype(ff->fname, dt, sizeof(dt))) {
135          Dmsg1(50, "Cannot determine drive type for \"%s\"\n", ff->fname);
136       } else {
137          for (i = 0; i < ff->drivetypes.size(); ++i) {
138             if (strcmp(dt, (char *)ff->drivetypes.get(i)) == 0) {
139                Dmsg2(100, "Accepting drive type %s for \"%s\"\n", dt, ff->fname);
140                accept = true;
141                break;
142             }
143             Dmsg3(200, "drive type %s for \"%s\" does not match %s\n", dt,
144                   ff->fname, ff->drivetypes.get(i));
145          }
146       }
147    }
148    return accept;
149 }
150
151 /*
152  * This function determines whether we can use getattrlist()
153  * It's odd, but we have to use the function to determine that...
154  * Also, the man pages talk about things as if they were implemented.
155  *
156  * On Mac OS X, this succesfully differentiates between HFS+ and UFS
157  * volumes, which makes me trust it is OK for others, too.
158  */
159 static bool volume_has_attrlist(const char *fname)
160 {
161 #ifdef HAVE_DARWIN_OS
162    struct statfs st;
163    struct volinfo_struct {
164       unsigned long length;               /* Mandatory field */
165       vol_capabilities_attr_t info;       /* Volume capabilities */
166    } vol;
167    struct attrlist attrList;
168
169    memset(&attrList, 0, sizeof(attrList));
170    attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
171    attrList.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES;
172    if (statfs(fname, &st) == 0) {
173       /* We need to check on the mount point */
174       if (getattrlist(st.f_mntonname, &attrList, &vol, sizeof(vol), FSOPT_NOFOLLOW) == 0
175             && (vol.info.capabilities[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_ATTRLIST)
176             && (vol.info.valid[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_ATTRLIST)) {
177          return true;
178       }
179    }
180 #endif
181    return false;
182 }
183
184 /*
185  * Find a single file.
186  * handle_file is the callback for handling the file.
187  * p is the filename
188  * parent_device is the device we are currently on
189  * top_level is 1 when not recursing or 0 when
190  *  descending into a directory.
191  */
192 int
193 find_one_file(JCR *jcr, FF_PKT *ff_pkt, 
194                int handle_file(FF_PKT *ff, void *hpkt, bool top_level),
195                void *pkt, char *fname, dev_t parent_device, bool top_level)
196 {
197    struct utimbuf restore_times;
198    int rtn_stat;
199    int len;
200
201    ff_pkt->fname = ff_pkt->link = fname;
202
203    if (lstat(fname, &ff_pkt->statp) != 0) {
204        /* Cannot stat file */
205        ff_pkt->type = FT_NOSTAT;
206        ff_pkt->ff_errno = errno;
207        return handle_file(ff_pkt, pkt, top_level);
208    }
209
210    Dmsg1(300, "File ----: %s\n", fname);
211
212    /* Save current times of this directory in case we need to
213     * reset them because the user doesn't want them changed.
214     */
215    restore_times.actime = ff_pkt->statp.st_atime;
216    restore_times.modtime = ff_pkt->statp.st_mtime;
217
218    /*
219     * We check for allowed fstypes and drivetypes at top_level and fstype change (below).
220     */
221    if (top_level) {
222       if (!accept_fstype(ff_pkt, NULL)) {
223          ff_pkt->type = FT_INVALIDFS;
224          if (ff_pkt->flags & FO_KEEPATIME) {
225             utime(fname, &restore_times);
226          }
227
228          char fs[100];
229
230          if (!fstype(ff_pkt->fname, fs, sizeof(fs))) {
231              bstrncpy(fs, "unknown", sizeof(fs));
232          }
233
234          Jmsg(jcr, M_INFO, 0, _("Top level directory \"%s\" has unlisted fstype \"%s\"\n"), fname, fs);
235          return 1;      /* Just ignore this error - or the whole backup is cancelled */
236       }
237       if (!accept_drivetype(ff_pkt, NULL)) {
238          ff_pkt->type = FT_INVALIDDT;
239          if (ff_pkt->flags & FO_KEEPATIME) {
240             utime(fname, &restore_times);
241          }
242
243          char dt[100];
244
245          if (!drivetype(ff_pkt->fname, dt, sizeof(dt))) {
246              bstrncpy(dt, "unknown", sizeof(dt));
247          }
248
249          Jmsg(jcr, M_INFO, 0, _("Top level directory \"%s\" has an unlisted drive type \"%s\"\n"), fname, dt);
250          return 1;      /* Just ignore this error - or the whole backup is cancelled */
251       }
252       ff_pkt->volhas_attrlist = volume_has_attrlist(fname);
253    }
254
255    /*
256     * If this is an Incremental backup, see if file was modified
257     * since our last "save_time", presumably the last Full save
258     * or Incremental.
259     */
260    if (ff_pkt->incremental && !S_ISDIR(ff_pkt->statp.st_mode)) {
261       Dmsg1(300, "Non-directory incremental: %s\n", ff_pkt->fname);
262       /* Not a directory */
263       if (ff_pkt->statp.st_mtime < ff_pkt->save_time
264           && ((ff_pkt->flags & FO_MTIMEONLY) ||
265               ff_pkt->statp.st_ctime < ff_pkt->save_time)) {
266          /* Incremental option, file not changed */
267          ff_pkt->type = FT_NOCHG;
268          return handle_file(ff_pkt, pkt, top_level);
269       }
270    }
271
272 #ifdef HAVE_DARWIN_OS
273    if (ff_pkt->flags & FO_HFSPLUS && ff_pkt->volhas_attrlist
274          && S_ISREG(ff_pkt->statp.st_mode)) {
275        /* TODO: initialise attrList once elsewhere? */
276        struct attrlist attrList;
277        memset(&attrList, 0, sizeof(attrList));
278        attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
279        attrList.commonattr = ATTR_CMN_FNDRINFO;
280        attrList.fileattr = ATTR_FILE_RSRCLENGTH;
281        if (getattrlist(fname, &attrList, &ff_pkt->hfsinfo,
282                 sizeof(ff_pkt->hfsinfo), FSOPT_NOFOLLOW) != 0) {
283           ff_pkt->type = FT_NOSTAT;
284           ff_pkt->ff_errno = errno;
285           return handle_file(ff_pkt, pkt, top_level);
286        }
287    }
288 #endif
289
290 /* ***FIXME*** implement this */
291 #if xxxxxxx
292    /* See if we are trying to dump the archive.  */
293    if (ar_dev && ff_pkt->statp.st_dev == ar_dev && ff_pkt->statp.st_ino == ar_ino) {
294        ff_pkt->type = FT_ISARCH;
295        return handle_file(ff_pkt, pkt, top_level);
296    }
297 #endif
298    ff_pkt->LinkFI = 0;
299    /*
300     * Handle hard linked files
301     *
302     * Maintain a list of hard linked files already backed up. This
303     *  allows us to ensure that the data of each file gets backed
304     *  up only once.
305     */
306    if (!(ff_pkt->flags & FO_NO_HARDLINK)
307        && ff_pkt->statp.st_nlink > 1
308        && (S_ISREG(ff_pkt->statp.st_mode)
309            || S_ISCHR(ff_pkt->statp.st_mode)
310            || S_ISBLK(ff_pkt->statp.st_mode)
311            || S_ISFIFO(ff_pkt->statp.st_mode)
312            || S_ISSOCK(ff_pkt->statp.st_mode))) {
313
314        struct f_link *lp;
315        if (ff_pkt->linkhash == NULL) {
316            ff_pkt->linkhash = (link_t **)bmalloc(LINK_HASHTABLE_SIZE * sizeof(link_t *));
317            memset(ff_pkt->linkhash, 0, LINK_HASHTABLE_SIZE * sizeof(link_t *));
318        }
319        const int linkhash = LINKHASH(ff_pkt->statp);
320
321       /* Search link list of hard linked files */
322        for (lp = ff_pkt->linkhash[linkhash]; lp; lp = lp->next)
323          if (lp->ino == (ino_t)ff_pkt->statp.st_ino &&
324              lp->dev == (dev_t)ff_pkt->statp.st_dev) {
325              /* If we have already backed up the hard linked file don't do it again */
326              if (strcmp(lp->name, fname) == 0) {
327                 return 1;             /* ignore */
328              }
329              ff_pkt->link = lp->name;
330              ff_pkt->type = FT_LNKSAVED;       /* Handle link, file already saved */
331              ff_pkt->LinkFI = lp->FileIndex;
332              return handle_file(ff_pkt, pkt, top_level);
333          }
334
335       /* File not previously dumped. Chain it into our list. */
336       len = strlen(fname) + 1;
337       lp = (struct f_link *)bmalloc(sizeof(struct f_link) + len);
338       lp->ino = ff_pkt->statp.st_ino;
339       lp->dev = ff_pkt->statp.st_dev;
340       bstrncpy(lp->name, fname, len);
341        lp->next = ff_pkt->linkhash[linkhash];
342        ff_pkt->linkhash[linkhash] = lp;
343       ff_pkt->linked = lp;            /* mark saved link */
344    } else {
345       ff_pkt->linked = NULL;
346    }
347
348    /* This is not a link to a previously dumped file, so dump it.  */
349    if (S_ISREG(ff_pkt->statp.st_mode)) {
350       boffset_t sizeleft;
351
352       sizeleft = ff_pkt->statp.st_size;
353
354       /* Don't bother opening empty, world readable files.  Also do not open
355          files when archive is meant for /dev/null.  */
356       if (ff_pkt->null_output_device || (sizeleft == 0
357               && MODE_RALL == (MODE_RALL & ff_pkt->statp.st_mode))) {
358          ff_pkt->type = FT_REGE;
359       } else {
360          ff_pkt->type = FT_REG;
361       }
362       rtn_stat = handle_file(ff_pkt, pkt, top_level);
363       if (ff_pkt->linked) {
364          ff_pkt->linked->FileIndex = ff_pkt->FileIndex;
365       }
366       if (ff_pkt->flags & FO_KEEPATIME) {
367          utime(fname, &restore_times);
368       }       
369       return rtn_stat;
370
371
372    } else if (S_ISLNK(ff_pkt->statp.st_mode)) {  /* soft link */
373       int size;
374       char *buffer = (char *)alloca(path_max + name_max + 102);
375
376       size = readlink(fname, buffer, path_max + name_max + 101);
377       if (size < 0) {
378          /* Could not follow link */
379          ff_pkt->type = FT_NOFOLLOW;
380          ff_pkt->ff_errno = errno;
381          rtn_stat = handle_file(ff_pkt, pkt, top_level);
382          if (ff_pkt->linked) {
383             ff_pkt->linked->FileIndex = ff_pkt->FileIndex;
384          }
385          return rtn_stat;
386       }
387       buffer[size] = 0;
388       ff_pkt->link = buffer;          /* point to link */
389       ff_pkt->type = FT_LNK;          /* got a real link */
390       rtn_stat = handle_file(ff_pkt, pkt, top_level);
391       if (ff_pkt->linked) {
392          ff_pkt->linked->FileIndex = ff_pkt->FileIndex;
393       }
394       return rtn_stat;
395
396    } else if (S_ISDIR(ff_pkt->statp.st_mode)) {
397       DIR *directory;
398       struct dirent *entry, *result;
399       char *link;
400       int link_len;
401       int len;
402       int status;
403       dev_t our_device = ff_pkt->statp.st_dev;
404       bool recurse = true;
405       bool volhas_attrlist = ff_pkt->volhas_attrlist;    /* Remember this if we recurse */
406
407       /*
408        * If we are using Win32 (non-portable) backup API, don't check
409        *  access as everything is more complicated, and
410        *  in principle, we should be able to access everything.
411        */
412       if (!have_win32_api() || (ff_pkt->flags & FO_PORTABLE)) {
413          if (access(fname, R_OK) == -1 && geteuid() != 0) {
414             /* Could not access() directory */
415             ff_pkt->type = FT_NOACCESS;
416             ff_pkt->ff_errno = errno;
417             rtn_stat = handle_file(ff_pkt, pkt, top_level);
418             if (ff_pkt->linked) {
419                ff_pkt->linked->FileIndex = ff_pkt->FileIndex;
420             }
421             return rtn_stat;
422          }
423       }
424
425       /* Build a canonical directory name with a trailing slash in link var */
426       len = strlen(fname);
427       link_len = len + 200;
428       link = (char *)bmalloc(link_len + 2);
429       bstrncpy(link, fname, link_len);
430       /* Strip all trailing slashes */
431       while (len >= 1 && IsPathSeparator(link[len - 1]))
432         len--;
433       link[len++] = '/';             /* add back one */
434       link[len] = 0;
435
436       ff_pkt->link = link;
437       if (ff_pkt->incremental &&
438           (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
439              ((ff_pkt->flags & FO_MTIMEONLY) ||
440                ff_pkt->statp.st_ctime < ff_pkt->save_time))) {
441          /* Incremental option, directory entry not changed */
442          ff_pkt->type = FT_DIRNOCHG;
443       } else {
444          ff_pkt->type = FT_DIRBEGIN;
445       }
446       /*
447        * Note, we return the directory to the calling program (handle_file)
448        * when we first see the directory (FT_DIRBEGIN.
449        * This allows the program to apply matches and make a
450        * choice whether or not to accept it.  If it is accepted, we
451        * do not immediately save it, but do so only after everything
452        * in the directory is seen (i.e. the FT_DIREND).
453        */
454       rtn_stat = handle_file(ff_pkt, pkt, top_level);
455       if (rtn_stat < 1) {             /* ignore or error status */
456          free(link);
457          return rtn_stat;
458       }
459       /* Done with DIRBEGIN, next call will be DIREND */
460       if (ff_pkt->type == FT_DIRBEGIN) {
461          ff_pkt->type = FT_DIREND;
462       }
463
464       /*
465        * Create a temporary ff packet for this directory
466        *   entry, and defer handling the directory until
467        *   we have recursed into it.  This saves the
468        *   directory after all files have been processed, and
469        *   during the restore, the directory permissions will
470        *   be reset after all the files have been restored.
471        */
472       Dmsg1(300, "Create temp ff packet for dir: %s\n", ff_pkt->fname);
473       FF_PKT *dir_ff_pkt = (FF_PKT *)bmalloc(sizeof(FF_PKT));
474       memcpy(dir_ff_pkt, ff_pkt, sizeof(FF_PKT));
475       dir_ff_pkt->fname = bstrdup(ff_pkt->fname);
476       dir_ff_pkt->link = bstrdup(ff_pkt->link);
477       dir_ff_pkt->sys_fname = get_pool_memory(PM_FNAME);
478       dir_ff_pkt->included_files_list = NULL;
479       dir_ff_pkt->excluded_files_list = NULL;
480       dir_ff_pkt->excluded_paths_list = NULL;
481       dir_ff_pkt->linkhash = NULL;
482
483       /*
484        * Do not descend into subdirectories (recurse) if the
485        * user has turned it off for this directory.
486        *
487        * If we are crossing file systems, we are either not allowed
488        * to cross, or we may be restricted by a list of permitted
489        * file systems.
490        */
491       if (!top_level && ff_pkt->flags & FO_NO_RECURSION) {
492          ff_pkt->type = FT_NORECURSE;
493          recurse = false;
494       } else if (!top_level && parent_device != ff_pkt->statp.st_dev) {
495          if(!(ff_pkt->flags & FO_MULTIFS)) {
496             ff_pkt->type = FT_NOFSCHG;
497             recurse = false;
498          } else if (!accept_fstype(ff_pkt, NULL)) {
499             ff_pkt->type = FT_INVALIDFS;
500             recurse = false;
501          } else {
502             ff_pkt->volhas_attrlist = volume_has_attrlist(fname);
503          }
504       }
505       /* If not recursing, just backup dir and return */
506       if (!recurse) {
507          rtn_stat = handle_file(ff_pkt, pkt, top_level);
508          if (ff_pkt->linked) {
509             ff_pkt->linked->FileIndex = ff_pkt->FileIndex;
510          }
511          free(link);
512          free_dir_ff_pkt(dir_ff_pkt);
513          ff_pkt->link = ff_pkt->fname;     /* reset "link" */
514          if (ff_pkt->flags & FO_KEEPATIME) {
515             utime(fname, &restore_times);
516          }
517          return rtn_stat;
518       }
519
520       ff_pkt->link = ff_pkt->fname;     /* reset "link" */
521
522       /*
523        * Descend into or "recurse" into the directory to read
524        *   all the files in it.
525        */
526       errno = 0;
527       if ((directory = opendir(fname)) == NULL) {
528          ff_pkt->type = FT_NOOPEN;
529          ff_pkt->ff_errno = errno;
530          rtn_stat = handle_file(ff_pkt, pkt, top_level);
531          if (ff_pkt->linked) {
532             ff_pkt->linked->FileIndex = ff_pkt->FileIndex;
533          }
534          free(link);
535          free_dir_ff_pkt(dir_ff_pkt);
536          return rtn_stat;
537       }
538
539       /*
540        * Process all files in this directory entry (recursing).
541        *    This would possibly run faster if we chdir to the directory
542        *    before traversing it.
543        */
544       rtn_stat = 1;
545       entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 100);
546       for ( ; !job_canceled(jcr); ) {
547          char *p, *q;
548          int i;
549
550          status  = readdir_r(directory, entry, &result);
551          if (status != 0 || result == NULL) {
552 //          Dmsg2(99, "readdir returned stat=%d result=0x%x\n",
553 //             status, (long)result);
554             break;
555          }
556          ASSERT(name_max+1 > (int)sizeof(struct dirent) + (int)NAMELEN(entry));
557          p = entry->d_name;
558          /* Skip `.', `..', and excluded file names.  */
559          if (p[0] == '\0' || (p[0] == '.' && (p[1] == '\0' ||
560              (p[1] == '.' && p[2] == '\0')))) {
561             continue;
562          }
563
564          if ((int)NAMELEN(entry) + len >= link_len) {
565              link_len = len + NAMELEN(entry) + 1;
566              link = (char *)brealloc(link, link_len + 1);
567          }
568          q = link + len;
569          for (i=0; i < (int)NAMELEN(entry); i++) {
570             *q++ = *p++;
571          }
572          *q = 0;
573          if (!file_is_excluded(ff_pkt, link)) {
574             rtn_stat = find_one_file(jcr, ff_pkt, handle_file, pkt, link, our_device, false);
575             if (ff_pkt->linked) {
576                ff_pkt->linked->FileIndex = ff_pkt->FileIndex;
577             }
578          }
579       }
580       closedir(directory);
581       free(link);
582       free(entry);
583
584       /*
585        * Now that we have recursed through all the files in the
586        *  directory, we "save" the directory so that after all
587        *  the files are restored, this entry will serve to reset
588        *  the directory modes and dates.  Temp directory values
589        *  were used without this record.
590        */
591       handle_file(dir_ff_pkt, pkt, top_level);       /* handle directory entry */
592       if (ff_pkt->linked) {
593          ff_pkt->linked->FileIndex = dir_ff_pkt->FileIndex;
594       }
595       free_dir_ff_pkt(dir_ff_pkt);
596
597       if (ff_pkt->flags & FO_KEEPATIME) {
598          utime(fname, &restore_times);
599       }
600       ff_pkt->volhas_attrlist = volhas_attrlist;      /* Restore value in case it changed. */
601       return rtn_stat;
602    } /* end check for directory */
603
604    /*
605     * If it is explicitly mentioned (i.e. top_level) and is
606     *  a block device, we do a raw backup of it or if it is
607     *  a fifo, we simply read it.
608     */
609 #ifdef HAVE_FREEBSD_OS
610    /*
611     * On FreeBSD, all block devices are character devices, so
612     *   to be able to read a raw disk, we need the check for
613     *   a character device.
614     * crw-r-----  1 root  operator  - 116, 0x00040002 Jun  9 19:32 /dev/ad0s3
615     * crw-r-----  1 root  operator  - 116, 0x00040002 Jun  9 19:32 /dev/rad0s3
616     */
617    if (top_level && (S_ISBLK(ff_pkt->statp.st_mode) || S_ISCHR(ff_pkt->statp.st_mode))) {
618 #else
619    if (top_level && S_ISBLK(ff_pkt->statp.st_mode)) {
620 #endif
621       ff_pkt->type = FT_RAW;          /* raw partition */
622    } else if (top_level && S_ISFIFO(ff_pkt->statp.st_mode) &&
623               ff_pkt->flags & FO_READFIFO) {
624       ff_pkt->type = FT_FIFO;
625    } else {
626       /* The only remaining types are special (character, ...) files */
627       ff_pkt->type = FT_SPEC;
628    }
629    rtn_stat = handle_file(ff_pkt, pkt, top_level);
630    if (ff_pkt->linked) {
631       ff_pkt->linked->FileIndex = ff_pkt->FileIndex;
632    }
633    return rtn_stat;
634 }
635
636 int term_find_one(FF_PKT *ff)
637 {
638    struct f_link *lp, *lc;
639    int count = 0;
640    int i;
641
642    
643    if (ff->linkhash == NULL) return 0;
644
645    for (i =0 ; i < LINK_HASHTABLE_SIZE; i ++) {
646    /* Free up list of hard linked files */
647        lp = ff->linkhash[i];
648        while (lp) {
649       lc = lp;
650       lp = lp->next;
651       if (lc) {
652          free(lc);
653          count++;
654       }
655    }
656        ff->linkhash[i] = NULL;
657    }
658    free(ff->linkhash);
659    ff->linkhash = NULL;
660    return count;
661 }