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 */
41 #define memcpy(d,s,n) bcopy ((s), (d), (n))
42 #define memmove(d,s,n) bcopy ((s), (d), (n))
43 #endif /* ! HAVE_MEMCPY */
46 #include <sys/types.h>
49 #include <netinet/in.h>
51 #include <netatalk/endian.h>
52 #include <atalk/afp.h>
53 #include <atalk/adouble.h>
54 #include <atalk/logger.h>
56 #include <atalk/cnid.h>
59 #include "directory.h"
70 u_int8_t attrs; /* File attributes (8 bits)*/
71 u_int8_t label; /* Label (8 bits)*/
72 char reserved[22]; /* Unknown (at least for now...) */
76 * 0x04 - has a custom icon
77 * 0x20 - name/icon is locked
91 /* This is our search-criteria structure. */
93 u_int32_t rbitmap; /* Request bitmap - which values should we check ? */
94 u_int16_t fbitmap, dbitmap; /* file & directory bitmap - which values should we return ? */
95 u_int16_t attr; /* File attributes */
96 time_t cdate; /* Creation date */
97 time_t mdate; /* Last modification date */
98 time_t bdate; /* Last backup date */
99 u_int32_t pdid; /* Parent DID */
100 u_int16_t offcnt; /* Offspring count */
101 struct finderinfo finfo; /* Finder info */
102 char lname[32]; /* Long name */
103 char utf8name[256]; /* UTF8 name */
107 * Directory tree search is recursive by its nature. But AFP specification
108 * requires FPCatSearch to pause after returning n results and be able to
109 * resume the search later. So we have to do recursive search using flat
110 * (iterative) algorithm and remember all directories to look into in an
111 * stack-like structure. The structure below is one item of directory stack.
115 char *m_name; /* Mac name */
116 char *u_name; /* unix name (== strrchr('/', path)) */
117 struct dir *dir; /* Structure describing this directory */
118 int pidx; /* Parent's dsitem structure index. */
119 int checked; /* Have we checked this directory ? */
120 char *path; /* absolute UNIX path to this directory */
125 static int cur_pos = 0; /* Saved position index (ID) - used to remember "position" across FPCatSearch calls */
126 static DIR *dirpos = NULL; /* UNIX structure describing currently opened directory. */
127 static int save_cidx = -1; /* Saved index of currently scanned directory. */
129 static struct dsitem *dstack = NULL; /* Directory stack data... */
130 static int dssize = 0; /* Directory stack (allocated) size... */
131 static int dsidx = 0; /* First free item index... */
133 static struct scrit c1, c2; /* search criteria */
135 /* Puts new item onto directory stack. */
136 static int addstack(char *uname, char *mname, struct dir *dir, int pidx)
141 /* check if we have some space on stack... */
142 if (dsidx >= dssize) {
144 dstack = realloc(dstack, dssize * sizeof(struct dsitem));
149 /* Put new element. Allocate and copy lname and path. */
150 ds = dstack + dsidx++;
151 if (!(ds->m_name = strdup(mname)))
156 l = strlen(dstack[pidx].path);
157 if (!(ds->path = malloc(l + strlen(uname) + 2) ))
159 strcpy(ds->path, dstack[pidx].path);
160 strcat(ds->path, "/");
161 strcat(ds->path, uname);
162 ds->u_name = ds->path +l +1;
170 /* Removes checked items from top of directory stack. Returns index of the first unchecked elements or -1. */
171 static int reducestack()
174 if (save_cidx != -1) {
181 if (dstack[dsidx-1].checked) {
183 free(dstack[dsidx].m_name);
184 free(dstack[dsidx].path);
185 /* Check if we need to free (or release) dir structures */
190 } /* reducestack() */
192 /* Clears directory stack. */
193 static void clearstack()
198 free(dstack[dsidx].m_name);
199 free(dstack[dsidx].path);
200 /* Check if we need to free (or release) dir structures */
204 /* Fills in dir field of dstack[cidx]. Must fill parent dirs' fields if needed... */
205 static int resolve_dir(struct vol *vol, int cidx)
207 struct dir *dir, *cdir;
209 if (dstack[cidx].dir != NULL)
212 if (dstack[cidx].pidx < 0)
215 if (dstack[dstack[cidx].pidx].dir == NULL && resolve_dir(vol, dstack[cidx].pidx) == 0)
218 cdir = dstack[dstack[cidx].pidx].dir;
221 if (strcmp(dir->d_m_name, dstack[cidx].m_name) == 0)
223 dir = (dir == cdir->d_child->d_prev) ? NULL : dir->d_next;
229 path.u_name = dstack[cidx].path;
230 if (of_stat(&path)==-1) {
231 syslog(LOG_DEBUG, "resolve_dir: stat %s: %s", dstack[cidx].path, strerror(errno));
234 path.m_name = dstack[cidx].m_name;
235 path.u_name = dstack[cidx].u_name;
236 /* adddir works with a filename not absolute pathname */
237 if ((dir = adddir(vol, cdir, &path)) == NULL)
240 dstack[cidx].dir = dir;
245 /* Looks up for an opened adouble structure, opens resource fork of selected file. */
246 static struct adouble *adl_lkup(struct path *path)
248 static struct adouble ad;
251 int isdir = S_ISDIR(path->st.st_mode);
253 if (!isdir && (of = of_findname(path))) {
256 memset(&ad, 0, sizeof(ad));
260 if ( ad_open( path->u_name, ADFLAGS_HF | (isdir)?ADFLAGS_DIR:0, O_RDONLY, 0, adp) < 0 ) {
266 #define CATPBIT_PARTIAL 31
267 /* Criteria checker. This function returns a 2-bit value. */
268 /* bit 0 means if checked file meets given criteria. */
269 /* bit 1 means if it is a directory and we should descent into it. */
271 * fname - our fname (translated to UNIX)
272 * cidx - index in directory stack
274 static int crit_check(struct vol *vol, struct path *path, int cidx) {
277 struct finderinfo *finfo = NULL, finderinfo;
278 struct adouble *adp = NULL;
279 time_t c_date, b_date;
281 if (S_ISDIR(path->st.st_mode)) {
286 else if (!c1.fbitmap)
289 /* Kind of optimization:
290 * -- first check things we've already have - filename
291 * -- last check things we get from ad_open()
292 * FIXME strmcp strstr (icase)
295 /* Check for filename */
296 if (c1.rbitmap & (1<<DIRPBIT_LNAME)) {
297 if (c1.rbitmap & (1<<CATPBIT_PARTIAL)) {
298 if (strcasestr(path->u_name, c1.lname) == NULL)
301 if (strcasecmp(path->u_name, c1.lname) != 0)
303 } /* if (c1.rbitmap & ... */
305 if ((c1.rbitmap & (1<<FILPBIT_PDINFO))) {
306 if (c1.rbitmap & (1<<CATPBIT_PARTIAL)) {
307 if (strcasestr(path->u_name, c1.utf8name) == NULL)
310 if (strcasecmp(path->u_name, c1.utf8name) != 0)
312 } /* if (c1.rbitmap & ... */
317 if ((unsigned)c2.mdate > 0x7fffffff)
318 c2.mdate = 0x7fffffff;
319 if ((unsigned)c2.cdate > 0x7fffffff)
320 c2.cdate = 0x7fffffff;
321 if ((unsigned)c2.bdate > 0x7fffffff)
322 c2.bdate = 0x7fffffff;
324 /* Check for modification date FIXME: should we look at adouble structure ? */
325 if ((c1.rbitmap & (1<<DIRPBIT_MDATE)))
326 if (path->st.st_mtime < c1.mdate || path->st.st_mtime > c2.mdate)
329 /* Check for creation date... */
330 if (c1.rbitmap & (1<<DIRPBIT_CDATE)) {
331 if (adp || (adp = adl_lkup(path))) {
332 if (ad_getdate(adp, AD_DATE_CREATE, (u_int32_t*)&c_date) >= 0)
333 c_date = AD_DATE_TO_UNIX(c_date);
334 else c_date = path->st.st_mtime;
335 } else c_date = path->st.st_mtime;
336 if (c_date < c1.cdate || c_date > c2.cdate)
340 /* Check for backup date... */
341 if (c1.rbitmap & (1<<DIRPBIT_BDATE)) {
342 if (adp || (adp == adl_lkup(path))) {
343 if (ad_getdate(adp, AD_DATE_BACKUP, (u_int32_t*)&b_date) >= 0)
344 b_date = AD_DATE_TO_UNIX(b_date);
345 else b_date = path->st.st_mtime;
346 } else b_date = path->st.st_mtime;
347 if (b_date < c1.bdate || b_date > c2.bdate)
351 /* Check attributes */
352 if ((c1.rbitmap & (1<<DIRPBIT_ATTR)) && c2.attr != 0) {
353 if (adp || (adp = adl_lkup(path))) {
354 ad_getattr(adp, &attr);
355 if ((attr & c2.attr) != c1.attr)
357 } else goto crit_check_ret;
360 /* Check file type ID */
361 if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.f_type != 0) {
363 adp = adl_lkup(path);
364 finfo = get_finderinfo(path->m_name, adp, &finderinfo);
365 if (finfo->f_type != c1.finfo.f_type)
369 /* Check creator ID */
370 if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.creator != 0) {
373 adp = adl_lkup(path);
374 finfo = get_finderinfo(path->m_name, adp, &finderinfo);
376 if (finfo->creator != c1.finfo.creator)
380 /* Check finder info attributes */
381 if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.attrs != 0) {
384 if (adp || (adp = adl_lkup(path))) {
385 finfo = (struct finderinfo*)ad_entry(adp, ADEID_FINDERI);
386 attrs = finfo->attrs;
388 else if (*path->u_name == '.') {
389 attrs = htons(FINDERINFO_INVISIBLE);
392 if ((attrs & c2.finfo.attrs) != c1.finfo.attrs)
397 if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.label != 0) {
398 if (adp || (adp = adl_lkup(path))) {
399 finfo = (struct finderinfo*)ad_entry(adp, ADEID_FINDERI);
400 if ((finfo->label & c2.finfo.label) != c1.finfo.label)
402 } else goto crit_check_ret;
404 /* FIXME: Attributes check ! */
406 /* All criteria are met. */
410 ad_close(adp, ADFLAGS_HF);
415 /* Adds an item to resultset. */
416 static int rslt_add(struct vol *vol, char *fname, short cidx, int isdir, char **rbuf)
419 int l = fname != NULL ? strlen(fname) : 0;
423 p0 = p[0] = cidx != -1 ? l + 7 : l + 5;
425 p[1] = isdir ? 128 : 0;
428 if (dstack[cidx].dir == NULL && resolve_dir(vol, cidx) == 0)
430 did = dstack[cidx].dir->d_did;
431 memcpy(p, &did, sizeof(did));
435 /* Fill offset of returned file name */
438 *p = (int)(p - *rbuf) - 1;
449 /* *rbuf[0] = (int)(p-*rbuf); */
453 static int rslt_add_ext ( struct vol *vol, struct path *path, char **buf, short cidx)
458 u_int16_t resultsize;
459 int isdir = S_ISDIR(path->st.st_mode);
461 if (dstack[cidx].dir == NULL && resolve_dir(vol, cidx) == 0)
464 p += sizeof(resultsize); /* Skip resultsize */
465 *p++ = isdir ? FILDIRBIT_ISDIR : FILDIRBIT_ISFILE; /* IsDir ? */
470 struct dir* dir = NULL;
472 dir = dirsearch_byname(dstack[cidx].dir, path->u_name);
474 if ((dir = adddir( vol, dstack[cidx].dir, path)) == NULL) {
478 ret = getdirparams(vol, c1.dbitmap, path, dir, p , &tbuf );
482 ret = getfilparams ( vol, c1.fbitmap, path, dstack[cidx].dir, p, &tbuf);
488 /* Make sure entry length is even */
494 resultsize = htons(tbuf);
495 memcpy ( *buf, &resultsize, sizeof(resultsize) );
505 "./../.AppleDouble/.AppleDB/Network Trash Folder/TheVolumeSettingsFolder/TheFindByContentFolder/.AppleDesktop/.Parent/"
507 /* This function performs search. It is called directly from afp_catsearch
508 * vol - volume we are searching on ...
509 * dir - directory we are starting from ...
510 * c1, c2 - search criteria
511 * rmatches - maximum number of matches we can return
512 * pos - position we've stopped recently
513 * rbuf - output buffer
514 * rbuflen - output buffer length
516 #define NUM_ROUNDS 100
517 static int catsearch(struct vol *vol, struct dir *dir,
518 int rmatches, int *pos, char *rbuf, u_int32_t *nrecs, int *rsize, int ext)
522 struct dirent *entry;
526 char *orig_dir = NULL;
527 int orig_dir_len = 128;
528 char *vpath = vol->v_path;
531 int num_rounds = NUM_ROUNDS;
533 if (*pos != 0 && *pos != cur_pos)
534 return AFPERR_CATCHNG;
536 /* FIXME: Category "offspring count ! */
538 /* So we are beginning... */
539 start_time = time(NULL);
541 /* We need to initialize all mandatory structures/variables and change working directory appropriate... */
544 if (dirpos != NULL) {
549 if (addstack("","", dir, -1) == -1) {
550 result = AFPERR_MISC;
553 dstack[0].path = strdup(vpath);
554 /* FIXME: Sometimes DID is given by client ! (correct this one above !) */
557 /* Save current path */
558 orig_dir = (char*)malloc(orig_dir_len);
559 while (getcwd(orig_dir, orig_dir_len-1)==NULL) {
560 if (errno != ERANGE) {
561 result = AFPERR_MISC;
565 orig_dir = realloc(orig_dir, orig_dir_len);
568 while ((cidx = reducestack()) != -1) {
570 dirpos = opendir(dstack[cidx].path);
571 if (dirpos == NULL) {
574 dstack[cidx].checked = 1;
579 result = AFPERR_NFILE;
584 result = AFPERR_MISC;
585 } /* switch (errno) */
588 chdir(dstack[cidx].path);
589 while ((entry=readdir(dirpos)) != NULL) {
592 if (!(fname = path.m_name = check_dirent(vol, entry->d_name)))
595 path.u_name = entry->d_name;
596 if (of_stat(&path) != 0) {
607 result = AFPERR_MISC;
609 } /* switch (errno) */
610 } /* if (stat(entry->d_name, &path.st) != 0) */
612 for (i = 0; fname[i] != 0; i++)
613 fname[i] = tolower(fname[i]);
615 ccr = crit_check(vol, &path, cidx);
616 /* bit 1 means that we have to descend into this directory. */
617 if ((ccr & 2) && S_ISDIR(path.st.st_mode)) {
618 if (addstack(entry->d_name, fname, NULL, cidx) == -1) {
619 result = AFPERR_MISC;
624 /* bit 0 means that criteria has been met */
627 r = rslt_add_ext ( vol, &path, &rrbuf, cidx);
632 (c1.fbitmap&(1<<FILPBIT_LNAME))|(c1.dbitmap&(1<<DIRPBIT_LNAME)) ?
634 (c1.fbitmap&(1<<FILPBIT_PDID))|(c1.dbitmap&(1<<DIRPBIT_PDID)) ?
636 S_ISDIR(path.st.st_mode), &rrbuf);
640 result = AFPERR_MISC;
644 /* Number of matches limit */
646 goto catsearch_pause;
647 /* Block size limit */
648 if (rrbuf - rbuf >= 448)
649 goto catsearch_pause;
651 /* MacOS 9 doesn't like servers executing commands longer than few seconds */
652 if (--num_rounds <= 0) {
653 if (start_time != time(NULL)) {
655 goto catsearch_pause;
657 num_rounds = NUM_ROUNDS;
659 } /* while ((entry=readdir(dirpos)) != NULL) */
662 dstack[cidx].checked = 1;
663 } /* while (current_idx = reducestack()) != -1) */
665 /* We have finished traversing our tree. Return EOF here. */
673 catsearch_end: /* Exiting catsearch: error condition */
674 *rsize = rrbuf - rbuf;
675 if (orig_dir != NULL) {
682 int catsearch_afp(AFPObj *obj, char *ibuf, int ibuflen,
683 char *rbuf, int *rbuflen, int ext)
687 u_int32_t rmatches, reserved;
692 unsigned char *spec1, *spec2, *bspec1, *bspec2;
694 memset(&c1, 0, sizeof(c1));
695 memset(&c2, 0, sizeof(c2));
698 memcpy(&vid, ibuf, sizeof(vid));
702 if ((vol = getvolbyvid(vid)) == NULL) {
706 memcpy(&rmatches, ibuf, sizeof(rmatches));
707 rmatches = ntohl(rmatches);
708 ibuf += sizeof(rmatches);
710 /* FIXME: (rl) should we check if reserved == 0 ? */
711 ibuf += sizeof(reserved);
713 memcpy(catpos, ibuf, sizeof(catpos));
714 ibuf += sizeof(catpos);
716 memcpy(&c1.fbitmap, ibuf, sizeof(c1.fbitmap));
717 c1.fbitmap = c2.fbitmap = ntohs(c1.fbitmap);
718 ibuf += sizeof(c1.fbitmap);
720 memcpy(&c1.dbitmap, ibuf, sizeof(c1.dbitmap));
721 c1.dbitmap = c2.dbitmap = ntohs(c1.dbitmap);
722 ibuf += sizeof(c1.dbitmap);
724 memcpy(&c1.rbitmap, ibuf, sizeof(c1.rbitmap));
725 c1.rbitmap = c2.rbitmap = ntohl(c1.rbitmap);
726 ibuf += sizeof(c1.rbitmap);
728 if (! (c1.fbitmap || c1.dbitmap)) {
729 return AFPERR_BITMAP;
735 /* Parse file specifications */
737 spec2 = ibuf + ibuf[0] + 2;
741 spec1++; bspec1 = spec1;
742 spec2++; bspec2 = spec2;
746 spec1 += 2; bspec1 = spec1;
747 spec2 += 2; bspec2 = spec2;
750 /* File attribute bits... */
751 if (c1.rbitmap & (1 << FILPBIT_ATTR)) {
752 memcpy(&c1.attr, ibuf, sizeof(c1.attr));
753 spec1 += sizeof(c1.attr);
754 c1.attr = ntohs(c1.attr);
755 memcpy(&c2.attr, ibuf, sizeof(c2.attr));
756 spec2 += sizeof(c1.attr);
757 c2.attr = ntohs(c2.attr);
761 if (c1.rbitmap & (1 << FILPBIT_PDID)) {
762 memcpy(&c1.pdid, spec1, sizeof(pdid));
763 spec1 += sizeof(c1.pdid);
764 memcpy(&c2.pdid, spec2, sizeof(pdid));
765 spec2 += sizeof(c2.pdid);
766 } /* FIXME: PDID - do we demarshall this argument ? */
769 if (c1.rbitmap & (1 << FILPBIT_CDATE)) {
770 memcpy(&c1.cdate, spec1, sizeof(c1.cdate));
771 spec1 += sizeof(c1.cdate);
772 c1.cdate = AD_DATE_TO_UNIX(c1.cdate);
773 memcpy(&c2.cdate, spec2, sizeof(c2.cdate));
774 spec2 += sizeof(c1.cdate);
775 ibuf += sizeof(c1.cdate);;
776 c2.cdate = AD_DATE_TO_UNIX(c2.cdate);
779 /* Modification date */
780 if (c1.rbitmap & (1 << FILPBIT_MDATE)) {
781 memcpy(&c1.mdate, spec1, sizeof(c1.mdate));
782 c1.mdate = AD_DATE_TO_UNIX(c1.mdate);
783 spec1 += sizeof(c1.mdate);
784 memcpy(&c2.mdate, spec2, sizeof(c2.mdate));
785 c2.mdate = AD_DATE_TO_UNIX(c2.mdate);
786 spec2 += sizeof(c1.mdate);
790 if (c1.rbitmap & (1 << FILPBIT_BDATE)) {
791 memcpy(&c1.bdate, spec1, sizeof(c1.bdate));
792 spec1 += sizeof(c1.bdate);
793 c1.bdate = AD_DATE_TO_UNIX(c1.bdate);
794 memcpy(&c2.bdate, spec2, sizeof(c2.bdate));
795 spec2 += sizeof(c2.bdate);
796 c1.bdate = AD_DATE_TO_UNIX(c2.bdate);
800 if (c1.rbitmap * (1 << FILPBIT_FINFO)) {
801 memcpy(&c1.finfo, spec1, sizeof(c1.finfo));
802 spec1 += sizeof(c1.finfo);
803 memcpy(&c2.finfo, spec2, sizeof(c2.finfo));
804 spec2 += sizeof(c2.finfo);
807 if ((c1.rbitmap & (1 << DIRPBIT_OFFCNT)) != 0) {
808 /* Offspring count - only directories */
809 if (c1.fbitmap == 0) {
810 memcpy(&c1.offcnt, spec1, sizeof(c1.offcnt));
811 spec1 += sizeof(c1.offcnt);
812 c1.offcnt = ntohs(c1.offcnt);
813 memcpy(&c2.offcnt, spec2, sizeof(c2.offcnt));
814 spec2 += sizeof(c2.offcnt);
815 c2.offcnt = ntohs(c2.offcnt);
817 else if (c1.dbitmap == 0) {
818 /* ressource fork length */
821 return AFPERR_BITMAP; /* error */
823 } /* Offspring count/ressource fork length */
826 if (c1.rbitmap & (1 << FILPBIT_LNAME)) {
827 /* Get the long filename */
828 memcpy(c1.lname, bspec1 + spec1[1] + 1, (bspec1 + spec1[1])[0]);
829 c1.lname[(bspec1 + spec1[1])[0]]= 0;
831 for (i = 0; c1.lname[i] != 0; i++)
832 c1.lname[i] = tolower(c1.lname[i]);
834 /* FIXME: do we need it ? It's always null ! */
835 memcpy(c2.lname, bspec2 + spec2[1] + 1, (bspec2 + spec2[1])[0]);
836 c2.lname[(bspec2 + spec2[1])[0]]= 0;
838 for (i = 0; c2.lname[i] != 0; i++)
839 c2.lname[i] = tolower(c2.lname[i]);
843 if (c1.rbitmap & (1 << FILPBIT_PDINFO)) {
848 memcpy(&namelen, spec1, sizeof(namelen));
849 namelen = ntohs (namelen);
851 spec1 = bspec1+namelen+4; /* Skip Unicode Hint */
854 memcpy(&namelen, spec1, sizeof(namelen));
855 namelen = ntohs (namelen);
856 if (namelen > 255) /* Safeguard */
859 memcpy (c1.utf8name, spec1+2, namelen);
860 c1.utf8name[(namelen+1)] =0;
862 /* convert charset */
863 tmppath = mtoupath(vol, c1.utf8name, 1);
864 memset (c1.utf8name, 0, 256);
865 memcpy (c1.utf8name, tmppath, MIN(strlen(tmppath), 255));
870 ret = catsearch(vol, vol->v_dir, rmatches, &catpos[0], rbuf+24, &nrecs, &rsize, ext);
871 memcpy(rbuf, catpos, sizeof(catpos));
872 rbuf += sizeof(catpos);
874 c1.fbitmap = htons(c1.fbitmap);
875 memcpy(rbuf, &c1.fbitmap, sizeof(c1.fbitmap));
876 rbuf += sizeof(c1.fbitmap);
878 c1.dbitmap = htons(c1.dbitmap);
879 memcpy(rbuf, &c1.dbitmap, sizeof(c1.dbitmap));
880 rbuf += sizeof(c1.dbitmap);
882 nrecs = htonl(nrecs);
883 memcpy(rbuf, &nrecs, sizeof(nrecs));
884 rbuf += sizeof(nrecs);
888 } /* catsearch_afp */
890 int afp_catsearch (AFPObj *obj, char *ibuf, int ibuflen,
891 char *rbuf, int *rbuflen)
893 return catsearch_afp( obj, ibuf, ibuflen, rbuf, rbuflen, 0);
897 int afp_catsearch_ext (AFPObj *obj, char *ibuf, int ibuflen,
898 char *rbuf, int *rbuflen)
900 return catsearch_afp( obj, ibuf, ibuflen, rbuf, rbuflen, 1);
903 /* FIXME: we need a clean separation between afp stubs and 'real' implementation */
904 /* (so, all buffer packing/unpacking should be done in stub, everything else
905 should be done in other functions) */