3 * Copyright (C) 1990, 1993 Regents of The University of Michigan
4 * All Rights Reserved. See COPYRIGHT
9 * This file contains FPCatSearch implementation. FPCatSearch performs
10 * file/directory search based on specified criteria. It is used by client
11 * to perform fast searches on (propably) big volumes. So, it has to be
14 * This implementation bypasses most of adouble/libatalk stuff as long as
15 * possible and does a standard filesystem search. It calls higher-level
16 * libatalk/afpd functions only when it is really needed, mainly while
17 * returning some non-UNIX information or filtering by non-UNIX criteria.
19 * Initial version written by Rafal Lewczuk <rlewczuk@pronet.pl>
25 #endif /* HAVE_CONFIG_H */
39 #define memcpy(d,s,n) bcopy ((s), (d), (n))
40 #define memmove(d,s,n) bcopy ((s), (d), (n))
41 #endif /* ! HAVE_MEMCPY */
44 #include <sys/types.h>
47 #include <netinet/in.h>
49 #include <netatalk/endian.h>
50 #include <atalk/afp.h>
51 #include <atalk/adouble.h>
53 #include <atalk/cnid.h>
56 #include "directory.h"
67 u_int8_t attrs; /* File attributes (8 bits)*/
68 u_int8_t label; /* Label (8 bits)*/
69 char reserved[22]; /* Unknown (at least for now...) */
73 * 0x04 - has a custom icon
74 * 0x20 - name/icon is locked
88 /* This is our search-criteria structure. */
90 u_int32_t rbitmap; /* Request bitmap - which values should we check ? */
91 u_int16_t fbitmap, dbitmap; /* file & directory bitmap - which values should we return ? */
92 u_int16_t attr; /* File attributes */
93 time_t cdate; /* Creation date */
94 time_t mdate; /* Last modification date */
95 time_t bdate; /* Last backup date */
96 u_int32_t pdid; /* Parent DID */
97 u_int16_t offcnt; /* Offspring count */
98 struct finderinfo finfo; /* Finder info */
99 char lname[32]; /* Long name */
103 * Directory tree search is recursive by its nature. But AFP specification
104 * requires FPCatSearch to pause after returning n results and be able to
105 * resume the search later. So we have to do recursive search using flat
106 * (iterative) algorithm and remember all directories to look into in an
107 * stack-like structure. The structure below is one item of directory stack.
111 char *m_name; /* Mac name */
112 char *u_name; /* unix name (== strrchr('/', path)) */
113 struct dir *dir; /* Structure describing this directory */
114 int pidx; /* Parent's dsitem structure index. */
115 int checked; /* Have we checked this directory ? */
116 char *path; /* absolute UNIX path to this directory */
121 static int cur_pos = 0; /* Saved position index (ID) - used to remember "position" across FPCatSearch calls */
122 static DIR *dirpos = NULL; /* UNIX structure describing currently opened directory. */
123 static int save_cidx = -1; /* Saved index of currently scanned directory. */
125 static struct dsitem *dstack = NULL; /* Directory stack data... */
126 static int dssize = 0; /* Directory stack (allocated) size... */
127 static int dsidx = 0; /* First free item index... */
129 static struct scrit c1, c2; /* search criteria */
131 /* Puts new item onto directory stack. */
132 static int addstack(char *uname, char *mname, struct dir *dir, int pidx)
137 /* check if we have some space on stack... */
138 if (dsidx >= dssize) {
140 dstack = realloc(dstack, dssize * sizeof(struct dsitem));
145 /* Put new element. Allocate and copy lname and path. */
146 ds = dstack + dsidx++;
147 if (!(ds->m_name = strdup(mname)))
152 l = strlen(dstack[pidx].path);
153 if (!(ds->path = malloc(l + strlen(uname) + 2) ))
155 strcpy(ds->path, dstack[pidx].path);
156 strcat(ds->path, "/");
157 strcat(ds->path, uname);
158 ds->u_name = ds->path +l +1;
166 /* Removes checked items from top of directory stack. Returns index of the first unchecked elements or -1. */
167 static int reducestack()
170 if (save_cidx != -1) {
177 if (dstack[dsidx-1].checked) {
179 free(dstack[dsidx].m_name);
180 free(dstack[dsidx].path);
181 /* Check if we need to free (or release) dir structures */
186 } /* reducestack() */
188 /* Clears directory stack. */
189 static void clearstack()
194 free(dstack[dsidx].m_name);
195 free(dstack[dsidx].path);
196 /* Check if we need to free (or release) dir structures */
200 /* Fills in dir field of dstack[cidx]. Must fill parent dirs' fields if needed... */
201 static int resolve_dir(struct vol *vol, int cidx)
203 struct dir *dir, *cdir;
205 if (dstack[cidx].dir != NULL)
208 if (dstack[cidx].pidx < 0)
211 if (dstack[dstack[cidx].pidx].dir == NULL && resolve_dir(vol, dstack[cidx].pidx) == 0)
214 cdir = dstack[dstack[cidx].pidx].dir;
217 if (strcmp(dir->d_m_name, dstack[cidx].m_name) == 0)
219 dir = (dir == cdir->d_child->d_prev) ? NULL : dir->d_next;
225 path.u_name = dstack[cidx].path;
226 if (of_stat(&path)==-1) {
227 syslog(LOG_DEBUG, "resolve_dir: stat %s: %s", dstack[cidx].path, strerror(errno));
230 path.m_name = dstack[cidx].m_name;
231 path.u_name = dstack[cidx].u_name;
232 /* adddir works with a filename not absolute pathname */
233 if ((dir = adddir(vol, cdir, &path)) == NULL)
236 dstack[cidx].dir = dir;
241 /* Looks up for an opened adouble structure, opens resource fork of selected file. */
242 static struct adouble *adl_lkup(struct path *path)
244 static struct adouble ad;
247 int isdir = S_ISDIR(path->st.st_mode);
249 if (!isdir && (of = of_findname(path))) {
252 memset(&ad, 0, sizeof(ad));
256 if ( ad_open( path->u_name, ADFLAGS_HF | (isdir)?ADFLAGS_DIR:0, O_RDONLY, 0, adp) < 0 ) {
262 #define CATPBIT_PARTIAL 31
263 /* Criteria checker. This function returns a 2-bit value. */
264 /* bit 0 means if checked file meets given criteria. */
265 /* bit 1 means if it is a directory and we should descent into it. */
267 * fname - our fname (translated to UNIX)
268 * cidx - index in directory stack
270 static int crit_check(struct vol *vol, struct path *path, int cidx) {
273 struct finderinfo *finfo = NULL, finderinfo;
274 struct adouble *adp = NULL;
275 time_t c_date, b_date;
277 if (S_ISDIR(path->st.st_mode)) {
282 else if (!c1.fbitmap)
285 /* Kind of optimization:
286 * -- first check things we've already have - filename
287 * -- last check things we get from ad_open()
288 * FIXME strmcp strstr (icase)
291 /* Check for filename */
292 if (c1.rbitmap & (1<<DIRPBIT_LNAME)) {
293 if (c1.rbitmap & (1<<CATPBIT_PARTIAL)) {
294 if (strcasestr(path->u_name, c1.lname) == NULL)
297 if (strcasecmp(path->u_name, c1.lname) != 0)
299 } /* if (c1.rbitmap & ... */
303 if ((unsigned)c2.mdate > 0x7fffffff)
304 c2.mdate = 0x7fffffff;
305 if ((unsigned)c2.cdate > 0x7fffffff)
306 c2.cdate = 0x7fffffff;
307 if ((unsigned)c2.bdate > 0x7fffffff)
308 c2.bdate = 0x7fffffff;
310 /* Check for modification date FIXME: should we look at adouble structure ? */
311 if ((c1.rbitmap & (1<<DIRPBIT_MDATE)))
312 if (path->st.st_mtime < c1.mdate || path->st.st_mtime > c2.mdate)
315 /* Check for creation date... */
316 if (c1.rbitmap & (1<<DIRPBIT_CDATE)) {
317 if (adp || (adp = adl_lkup(path))) {
318 if (ad_getdate(adp, AD_DATE_CREATE, (u_int32_t*)&c_date) >= 0)
319 c_date = AD_DATE_TO_UNIX(c_date);
320 else c_date = path->st.st_mtime;
321 } else c_date = path->st.st_mtime;
322 if (c_date < c1.cdate || c_date > c2.cdate)
326 /* Check for backup date... */
327 if (c1.rbitmap & (1<<DIRPBIT_BDATE)) {
328 if (adp || (adp == adl_lkup(path))) {
329 if (ad_getdate(adp, AD_DATE_BACKUP, (u_int32_t*)&b_date) >= 0)
330 b_date = AD_DATE_TO_UNIX(b_date);
331 else b_date = path->st.st_mtime;
332 } else b_date = path->st.st_mtime;
333 if (b_date < c1.bdate || b_date > c2.bdate)
337 /* Check attributes */
338 if ((c1.rbitmap & (1<<DIRPBIT_ATTR)) && c2.attr != 0) {
339 if (adp || (adp = adl_lkup(path))) {
340 ad_getattr(adp, &attr);
341 if ((attr & c2.attr) != c1.attr)
343 } else goto crit_check_ret;
346 /* Check file type ID */
347 if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.f_type != 0) {
349 adp = adl_lkup(path);
350 finfo = get_finderinfo(path->m_name, adp, &finderinfo);
351 if (finfo->f_type != c1.finfo.f_type)
355 /* Check creator ID */
356 if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.creator != 0) {
359 adp = adl_lkup(path);
360 finfo = get_finderinfo(path->m_name, adp, &finderinfo);
362 if (finfo->creator != c1.finfo.creator)
366 /* Check finder info attributes */
367 if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.attrs != 0) {
370 if (adp || (adp = adl_lkup(path))) {
371 finfo = (struct finderinfo*)ad_entry(adp, ADEID_FINDERI);
372 attrs = finfo->attrs;
374 else if (*path->u_name == '.') {
375 attrs = htons(FINDERINFO_INVISIBLE);
378 if ((attrs & c2.finfo.attrs) != c1.finfo.attrs)
383 if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.label != 0) {
384 if (adp || (adp = adl_lkup(path))) {
385 finfo = (struct finderinfo*)ad_entry(adp, ADEID_FINDERI);
386 if ((finfo->label & c2.finfo.label) != c1.finfo.label)
388 } else goto crit_check_ret;
390 /* FIXME: Attributes check ! */
392 /* All criteria are met. */
396 ad_close(adp, ADFLAGS_HF);
401 /* Adds an item to resultset. */
402 static int rslt_add(struct vol *vol, char *fname, short cidx, int isdir, char **rbuf)
405 int l = fname != NULL ? strlen(fname) : 0;
409 p0 = p[0] = cidx != -1 ? l + 7 : l + 5;
411 p[1] = isdir ? 128 : 0;
414 if (dstack[cidx].dir == NULL && resolve_dir(vol, cidx) == 0)
416 did = dstack[cidx].dir->d_did;
417 memcpy(p, &did, sizeof(did));
421 /* Fill offset of returned file name */
424 *p = (int)(p - *rbuf) - 1;
435 /* *rbuf[0] = (int)(p-*rbuf); */
440 "./../.AppleDouble/.AppleDB/Network Trash Folder/TheVolumeSettingsFolder/TheFindByContentFolder/.AppleDesktop/.Parent/"
442 /* This function performs search. It is called directly from afp_catsearch
443 * vol - volume we are searching on ...
444 * dir - directory we are starting from ...
445 * c1, c2 - search criteria
446 * rmatches - maximum number of matches we can return
447 * pos - position we've stopped recently
448 * rbuf - output buffer
449 * rbuflen - output buffer length
451 #define NUM_ROUNDS 100
452 static int catsearch(struct vol *vol, struct dir *dir,
453 int rmatches, int *pos, char *rbuf, u_int32_t *nrecs, int *rsize)
457 struct dirent *entry;
461 char *orig_dir = NULL;
462 int orig_dir_len = 128;
463 char *vpath = vol->v_path;
466 int num_rounds = NUM_ROUNDS;
468 if (*pos != 0 && *pos != cur_pos)
469 return AFPERR_CATCHNG;
471 /* FIXME: Category "offspring count ! */
473 /* So we are beginning... */
474 start_time = time(NULL);
476 /* We need to initialize all mandatory structures/variables and change working directory appropriate... */
479 if (dirpos != NULL) {
484 if (addstack("","", dir, -1) == -1) {
485 result = AFPERR_MISC;
488 dstack[0].path = strdup(vpath);
489 /* FIXME: Sometimes DID is given by client ! (correct this one above !) */
492 /* Save current path */
493 orig_dir = (char*)malloc(orig_dir_len);
494 while (getcwd(orig_dir, orig_dir_len-1)==NULL) {
495 if (errno != ERANGE) {
496 result = AFPERR_MISC;
500 orig_dir = realloc(orig_dir, orig_dir_len);
503 while ((cidx = reducestack()) != -1) {
505 dirpos = opendir(dstack[cidx].path);
506 if (dirpos == NULL) {
509 dstack[cidx].checked = 1;
514 result = AFPERR_NFILE;
519 result = AFPERR_MISC;
520 } /* switch (errno) */
523 chdir(dstack[cidx].path);
524 while ((entry=readdir(dirpos)) != NULL) {
527 if (!(fname = path.m_name = check_dirent(vol, entry->d_name)))
530 path.u_name = entry->d_name;
531 if (of_stat(&path) != 0) {
542 result = AFPERR_MISC;
544 } /* switch (errno) */
545 } /* if (stat(entry->d_name, &path.st) != 0) */
547 for (i = 0; fname[i] != 0; i++)
548 fname[i] = tolower(fname[i]);
550 ccr = crit_check(vol, &path, cidx);
551 /* bit 1 means that we have to descend into this directory. */
552 if ((ccr & 2) && S_ISDIR(path.st.st_mode)) {
553 if (addstack(entry->d_name, fname, NULL, cidx) == -1) {
554 result = AFPERR_MISC;
559 /* bit 0 means that criteria has been met */
562 (c1.fbitmap&(1<<FILPBIT_LNAME))|(c1.dbitmap&(1<<DIRPBIT_LNAME)) ?
564 (c1.fbitmap&(1<<FILPBIT_PDID))|(c1.dbitmap&(1<<DIRPBIT_PDID)) ?
566 S_ISDIR(path.st.st_mode), &rrbuf);
568 result = AFPERR_MISC;
572 /* Number of matches limit */
574 goto catsearch_pause;
575 /* Block size limit */
576 if (rrbuf - rbuf >= 448)
577 goto catsearch_pause;
579 /* MacOS 9 doesn't like servers executing commands longer than few seconds */
580 if (--num_rounds <= 0) {
581 if (start_time != time(NULL)) {
583 goto catsearch_pause;
585 num_rounds = NUM_ROUNDS;
587 } /* while ((entry=readdir(dirpos)) != NULL) */
590 dstack[cidx].checked = 1;
591 } /* while (current_idx = reducestack()) != -1) */
593 /* We have finished traversing our tree. Return EOF here. */
601 catsearch_end: /* Exiting catsearch: error condition */
602 *rsize = rrbuf - rbuf;
603 if (orig_dir != NULL) {
611 int afp_catsearch(AFPObj *obj, char *ibuf, int ibuflen,
612 char *rbuf, int *rbuflen)
616 u_int32_t rmatches, reserved;
621 unsigned char *spec1, *spec2, *bspec1, *bspec2;
623 memset(&c1, 0, sizeof(c1));
624 memset(&c2, 0, sizeof(c2));
627 memcpy(&vid, ibuf, sizeof(vid));
631 if ((vol = getvolbyvid(vid)) == NULL) {
635 memcpy(&rmatches, ibuf, sizeof(rmatches));
636 rmatches = ntohl(rmatches);
637 ibuf += sizeof(rmatches);
639 /* FIXME: (rl) should we check if reserved == 0 ? */
640 ibuf += sizeof(reserved);
642 memcpy(catpos, ibuf, sizeof(catpos));
643 ibuf += sizeof(catpos);
645 memcpy(&c1.fbitmap, ibuf, sizeof(c1.fbitmap));
646 c1.fbitmap = c2.fbitmap = ntohs(c1.fbitmap);
647 ibuf += sizeof(c1.fbitmap);
649 memcpy(&c1.dbitmap, ibuf, sizeof(c1.dbitmap));
650 c1.dbitmap = c2.dbitmap = ntohs(c1.dbitmap);
651 ibuf += sizeof(c1.dbitmap);
653 memcpy(&c1.rbitmap, ibuf, sizeof(c1.rbitmap));
654 c1.rbitmap = c2.rbitmap = ntohl(c1.rbitmap);
655 ibuf += sizeof(c1.rbitmap);
657 if (! (c1.fbitmap || c1.dbitmap)) {
658 return AFPERR_BITMAP;
661 /* Parse file specifications */
663 spec2 = ibuf + ibuf[0] + 2;
665 spec1 += 2; bspec1 = spec1;
666 spec2 += 2; bspec2 = spec2;
668 /* File attribute bits... */
669 if (c1.rbitmap & (1 << FILPBIT_ATTR)) {
670 memcpy(&c1.attr, ibuf, sizeof(c1.attr));
671 spec1 += sizeof(c1.attr);
672 c1.attr = ntohs(c1.attr);
673 memcpy(&c2.attr, ibuf, sizeof(c2.attr));
674 spec2 += sizeof(c1.attr);
675 c2.attr = ntohs(c2.attr);
679 if (c1.rbitmap & (1 << FILPBIT_PDID)) {
680 memcpy(&c1.pdid, spec1, sizeof(pdid));
681 spec1 += sizeof(c1.pdid);
682 memcpy(&c2.pdid, spec2, sizeof(pdid));
683 spec2 += sizeof(c2.pdid);
684 } /* FIXME: PDID - do we demarshall this argument ? */
687 if (c1.rbitmap & (1 << FILPBIT_CDATE)) {
688 memcpy(&c1.cdate, spec1, sizeof(c1.cdate));
689 spec1 += sizeof(c1.cdate);
690 c1.cdate = AD_DATE_TO_UNIX(c1.cdate);
691 memcpy(&c2.cdate, spec2, sizeof(c2.cdate));
692 spec2 += sizeof(c1.cdate);
693 ibuf += sizeof(c1.cdate);;
694 c2.cdate = AD_DATE_TO_UNIX(c2.cdate);
697 /* Modification date */
698 if (c1.rbitmap & (1 << FILPBIT_MDATE)) {
699 memcpy(&c1.mdate, spec1, sizeof(c1.mdate));
700 c1.mdate = AD_DATE_TO_UNIX(c1.mdate);
701 spec1 += sizeof(c1.mdate);
702 memcpy(&c2.mdate, spec2, sizeof(c2.mdate));
703 c2.mdate = AD_DATE_TO_UNIX(c2.mdate);
704 spec2 += sizeof(c1.mdate);
708 if (c1.rbitmap & (1 << FILPBIT_BDATE)) {
709 memcpy(&c1.bdate, spec1, sizeof(c1.bdate));
710 spec1 += sizeof(c1.bdate);
711 c1.bdate = AD_DATE_TO_UNIX(c1.bdate);
712 memcpy(&c2.bdate, spec2, sizeof(c2.bdate));
713 spec2 += sizeof(c2.bdate);
714 c1.bdate = AD_DATE_TO_UNIX(c2.bdate);
718 if (c1.rbitmap * (1 << FILPBIT_FINFO)) {
719 memcpy(&c1.finfo, spec1, sizeof(c1.finfo));
720 spec1 += sizeof(c1.finfo);
721 memcpy(&c2.finfo, spec2, sizeof(c2.finfo));
722 spec2 += sizeof(c2.finfo);
725 if ((c1.rbitmap & (1 << DIRPBIT_OFFCNT)) != 0) {
726 /* Offspring count - only directories */
727 if (c1.fbitmap == 0) {
728 memcpy(&c1.offcnt, spec1, sizeof(c1.offcnt));
729 spec1 += sizeof(c1.offcnt);
730 c1.offcnt = ntohs(c1.offcnt);
731 memcpy(&c2.offcnt, spec2, sizeof(c2.offcnt));
732 spec2 += sizeof(c2.offcnt);
733 c2.offcnt = ntohs(c2.offcnt);
735 else if (c1.dbitmap == 0) {
736 /* ressource fork length */
739 return AFPERR_BITMAP; /* error */
741 } /* Offspring count/ressource fork length */
744 if (c1.rbitmap & (1 << FILPBIT_LNAME)) {
745 /* Get the long filename */
746 memcpy(c1.lname, bspec1 + spec1[1] + 1, (bspec1 + spec1[1])[0]);
747 c1.lname[(bspec1 + spec1[1])[0]]= 0;
749 for (i = 0; c1.lname[i] != 0; i++)
750 c1.lname[i] = tolower(c1.lname[i]);
752 /* FIXME: do we need it ? It's always null ! */
753 memcpy(c2.lname, bspec2 + spec2[1] + 1, (bspec2 + spec2[1])[0]);
754 c2.lname[(bspec2 + spec2[1])[0]]= 0;
756 for (i = 0; c2.lname[i] != 0; i++)
757 c2.lname[i] = tolower(c2.lname[i]);
764 ret = catsearch(vol, vol->v_dir, rmatches, &catpos[0], rbuf+24, &nrecs, &rsize);
765 memcpy(rbuf, catpos, sizeof(catpos));
766 rbuf += sizeof(catpos);
768 c1.fbitmap = htons(c1.fbitmap);
769 memcpy(rbuf, &c1.fbitmap, sizeof(c1.fbitmap));
770 rbuf += sizeof(c1.fbitmap);
772 c1.dbitmap = htons(c1.dbitmap);
773 memcpy(rbuf, &c1.dbitmap, sizeof(c1.dbitmap));
774 rbuf += sizeof(c1.dbitmap);
776 nrecs = htonl(nrecs);
777 memcpy(rbuf, &nrecs, sizeof(nrecs));
778 rbuf += sizeof(nrecs);
782 } /* afp_catsearch */
784 /* FIXME: we need a clean separation between afp stubs and 'real' implementation */
785 /* (so, all buffer packing/unpacking should be done in stub, everything else
786 should be done in other functions) */