1/*
2 * Netatalk 2002 (c)
3 * Copyright (C) 1990, 1993 Regents of The University of Michigan
4 * Copyright (C) 2010 Frank Lahm
5 * All Rights Reserved. See COPYRIGHT
6 */
7
8
9/*
10 * This file contains FPCatSearch implementation. FPCatSearch performs
11 * file/directory search based on specified criteria. It is used by client
12 * to perform fast searches on (propably) big volumes. So, it has to be
13 * pretty fast.
14 *
15 * This implementation bypasses most of adouble/libatalk stuff as long as
16 * possible and does a standard filesystem search. It calls higher-level
17 * libatalk/afpd functions only when it is really needed, mainly while
18 * returning some non-UNIX information or filtering by non-UNIX criteria.
19 *
20 * Initial version written by Rafal Lewczuk <rlewczuk@pronet.pl>
21 *
22 * Starting with Netatalk 2.2 searching by name criteria utilizes the
23 * CNID database in conjunction with an enhanced cnid_dbd. This requires
24 * the use of cnidscheme:dbd for the searched volume, the new functionality
25 * is not built into cnidscheme:cdb.
26 */
27
28#ifdef HAVE_CONFIG_H
29#include "config.h"
30#endif /* HAVE_CONFIG_H */
31
32#include <stdio.h>
33#include <stdlib.h>
34#include <errno.h>
35#include <ctype.h>
36#include <string.h>
37#include <time.h>
38#include <string.h>
39#include <sys/file.h>
40#include <netinet/in.h>
41
42#include <atalk/afp.h>
43#include <atalk/adouble.h>
44#include <atalk/logger.h>
45#include <atalk/cnid.h>
46#include <atalk/cnid_dbd_private.h>
47#include <atalk/util.h>
48#include <atalk/bstradd.h>
49#include <atalk/unicode.h>
50#include <atalk/globals.h>
51
52#include "desktop.h"
53#include "directory.h"
54#include "dircache.h"
55#include "file.h"
56#include "volume.h"
57#include "filedir.h"
58#include "fork.h"
59
60
61struct finderinfo {
62	u_int32_t f_type;
63	u_int32_t creator;
64	u_int16_t attrs;    /* File attributes (high 8 bits)*/
65	u_int16_t label;    /* Label (low 8 bits )*/
66	char reserved[22]; /* Unknown (at least for now...) */
67};
68
69typedef char packed_finder[ADEDLEN_FINDERI];
70
71/* Known attributes:
72 * 0x04 - has a custom icon
73 * 0x20 - name/icon is locked
74 * 0x40 - is invisible
75 * 0x80 - is alias
76 *
77 * Known labels:
78 * 0x02 - project 2
79 * 0x04 - project 1
80 * 0x06 - personal
81 * 0x08 - cool
82 * 0x0a - in progress
83 * 0x0c - hot
84 * 0x0e - essential
85 */
86
87/* This is our search-criteria structure. */
88struct scrit {
89	u_int32_t rbitmap;          /* Request bitmap - which values should we check ? */
90	u_int16_t fbitmap, dbitmap; /* file & directory bitmap - which values should we return ? */
91	u_int16_t attr;             /* File attributes */
92	time_t cdate;               /* Creation date */
93	time_t mdate;               /* Last modification date */
94	time_t bdate;               /* Last backup date */
95	u_int32_t pdid;             /* Parent DID */
96    u_int16_t offcnt;           /* Offspring count */
97	struct finderinfo finfo;    /* Finder info */
98	char lname[64];             /* Long name */
99	char utf8name[514];         /* UTF8 or UCS2 name */ /* for convert_charset dest_len parameter +2 */
100};
101
102/*
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.
108 *
109 */
110struct dsitem {
111//	struct dir *dir; /* Structure describing this directory */
112//  cnid_t did;      /* CNID of this directory           */
113	int pidx;        /* Parent's dsitem structure index. */
114	int checked;     /* Have we checked this directory ? */
115	int path_len;
116	char *path;      /* absolute UNIX path to this directory */
117};
118
119
120#define DS_BSIZE 128
121static int save_cidx = -1; /* Saved index of currently scanned directory. */
122
123static struct dsitem *dstack = NULL; /* Directory stack data... */
124static int dssize = 0;  	     /* Directory stack (allocated) size... */
125static int dsidx = 0;   	     /* First free item index... */
126
127static struct scrit c1, c2;          /* search criteria */
128
129/* Puts new item onto directory stack. */
130static int addstack(char *uname, struct dir *dir, int pidx)
131{
132	struct dsitem *ds;
133	size_t         l, u;
134
135	/* check if we have some space on stack... */
136	if (dsidx >= dssize) {
137		dssize += DS_BSIZE;
138		dstack = realloc(dstack, dssize * sizeof(struct dsitem));
139		if (dstack == NULL)
140			return -1;
141	}
142
143	/* Put new element. Allocate and copy lname and path. */
144	ds = dstack + dsidx++;
145//	ds->did = dir->d_did;
146	ds->pidx = pidx;
147	ds->checked = 0;
148	if (pidx >= 0) {
149	    l = dstack[pidx].path_len;
150	    u = strlen(uname) +1;
151	    if (!(ds->path = malloc(l + u + 1) ))
152			return -1;
153		memcpy(ds->path, dstack[pidx].path, l);
154		ds->path[l] = '/';
155		memcpy(&ds->path[l+1], uname, u);
156		ds->path_len = l +u;
157	}
158	else {
159	    ds->path = strdup(uname);
160		ds->path_len = strlen(uname);
161	}
162	return 0;
163}
164
165/* Removes checked items from top of directory stack. Returns index of the first unchecked elements or -1. */
166static int reducestack(void)
167{
168	int r;
169	if (save_cidx != -1) {
170		r = save_cidx;
171		save_cidx = -1;
172		return r;
173	}
174
175	while (dsidx > 0) {
176		if (dstack[dsidx-1].checked) {
177			dsidx--;
178			free(dstack[dsidx].path);
179		} else
180			return dsidx - 1;
181	}
182	return -1;
183}
184
185/* Clears directory stack. */
186static void clearstack(void)
187{
188	save_cidx = -1;
189	while (dsidx > 0) {
190		dsidx--;
191		free(dstack[dsidx].path);
192	}
193}
194
195/* Looks up for an opened adouble structure, opens resource fork of selected file.
196 * FIXME What about noadouble?
197*/
198static struct adouble *adl_lkup(struct vol *vol, struct path *path, struct adouble *adp)
199{
200	static struct adouble ad;
201
202	struct ofork *of;
203	int isdir;
204
205	if (adp)
206	    return adp;
207
208	isdir  = S_ISDIR(path->st.st_mode);
209
210	if (!isdir && (of = of_findname(path))) {
211		adp = of->of_ad;
212	} else {
213		ad_init(&ad, vol->v_adouble, vol->v_ad_options);
214		adp = &ad;
215	}
216
217    if ( ad_metadata( path->u_name, ((isdir) ? ADFLAGS_DIR : 0), adp) < 0 ) {
218        adp = NULL; /* FIXME without resource fork adl_lkup will be call again */
219    }
220
221	return adp;
222}
223
224/* -------------------- */
225static struct finderinfo *unpack_buffer(struct finderinfo *finfo, char *buffer)
226{
227	memcpy(&finfo->f_type,  buffer +FINDERINFO_FRTYPEOFF, sizeof(finfo->f_type));
228	memcpy(&finfo->creator, buffer +FINDERINFO_FRCREATOFF, sizeof(finfo->creator));
229	memcpy(&finfo->attrs,   buffer +FINDERINFO_FRFLAGOFF, sizeof(finfo->attrs));
230	memcpy(&finfo->label,   buffer +FINDERINFO_FRFLAGOFF, sizeof(finfo->label));
231	finfo->attrs &= 0xff00; /* high 8 bits */
232	finfo->label &= 0xff;   /* low 8 bits */
233
234	return finfo;
235}
236
237/* -------------------- */
238static struct finderinfo *
239unpack_finderinfo(struct vol *vol, struct path *path, struct adouble **adp, struct finderinfo *finfo, int islnk)
240{
241	packed_finder  buf;
242	void           *ptr;
243
244    *adp = adl_lkup(vol, path, *adp);
245	ptr = get_finderinfo(vol, path->u_name, *adp, &buf,islnk);
246	return unpack_buffer(finfo, ptr);
247}
248
249/* -------------------- */
250#define CATPBIT_PARTIAL 31
251/* Criteria checker. This function returns a 2-bit value. */
252/* bit 0 means if checked file meets given criteria. */
253/* bit 1 means if it is a directory and we should descent into it. */
254/* uname - UNIX name
255 * fname - our fname (translated to UNIX)
256 * cidx - index in directory stack
257 */
258static int crit_check(struct vol *vol, struct path *path) {
259	int result = 0;
260	u_int16_t attr, flags = CONV_PRECOMPOSE;
261	struct finderinfo *finfo = NULL, finderinfo;
262	struct adouble *adp = NULL;
263	time_t c_date, b_date;
264	u_int32_t ac_date, ab_date;
265	static char convbuf[514]; /* for convert_charset dest_len parameter +2 */
266	size_t len;
267    int islnk;
268    islnk=S_ISLNK(path->st.st_mode);
269
270	if (S_ISDIR(path->st.st_mode)) {
271		if (!c1.dbitmap)
272			return 0;
273	}
274	else {
275		if (!c1.fbitmap)
276			return 0;
277
278		/* compute the Mac name
279		 * first try without id (it's slow to find it)
280		 * An other option would be to call get_id in utompath but
281		 * we need to pass parent dir
282		*/
283        if (!(path->m_name = utompath(vol, path->u_name, 0 , utf8_encoding()) )) {
284        	/*retry with the right id */
285
286        	cnid_t id;
287
288        	adp = adl_lkup(vol, path, adp);
289        	id = get_id(vol, adp, &path->st, path->d_dir->d_did, path->u_name, strlen(path->u_name));
290        	if (!id) {
291        		/* FIXME */
292        		return 0;
293        	}
294        	/* save the id for getfilparm */
295        	path->id = id;
296        	if (!(path->m_name = utompath(vol, path->u_name, id , utf8_encoding()))) {
297        		return 0;
298        	}
299        }
300	}
301
302	/* Kind of optimization:
303	 * -- first check things we've already have - filename
304	 * -- last check things we get from ad_open()
305	 * FIXME strmcp strstr (icase)
306	 */
307
308	/* Check for filename */
309	if ((c1.rbitmap & (1<<DIRPBIT_LNAME))) {
310		if ( (size_t)(-1) == (len = convert_string(vol->v_maccharset, CH_UCS2, path->m_name, -1, convbuf, 512)) )
311			goto crit_check_ret;
312
313		if ((c1.rbitmap & (1<<CATPBIT_PARTIAL))) {
314			if (strcasestr_w( (ucs2_t*) convbuf, (ucs2_t*) c1.lname) == NULL)
315				goto crit_check_ret;
316		} else
317			if (strcasecmp_w((ucs2_t*) convbuf, (ucs2_t*) c1.lname) != 0)
318				goto crit_check_ret;
319	}
320
321	if ((c1.rbitmap & (1<<FILPBIT_PDINFO))) {
322		if ( (size_t)(-1) == (len = convert_charset( CH_UTF8_MAC, CH_UCS2, CH_UTF8, path->m_name, strlen(path->m_name), convbuf, 512, &flags))) {
323			goto crit_check_ret;
324		}
325
326		if (c1.rbitmap & (1<<CATPBIT_PARTIAL)) {
327			if (strcasestr_w((ucs2_t *) convbuf, (ucs2_t*)c1.utf8name) == NULL)
328				goto crit_check_ret;
329		} else
330			if (strcasecmp_w((ucs2_t *)convbuf, (ucs2_t*)c1.utf8name) != 0)
331				goto crit_check_ret;
332	}
333
334
335	/* FIXME */
336	if ((unsigned)c2.mdate > 0x7fffffff)
337		c2.mdate = 0x7fffffff;
338	if ((unsigned)c2.cdate > 0x7fffffff)
339		c2.cdate = 0x7fffffff;
340	if ((unsigned)c2.bdate > 0x7fffffff)
341		c2.bdate = 0x7fffffff;
342
343	/* Check for modification date */
344	if ((c1.rbitmap & (1<<DIRPBIT_MDATE))) {
345		if (path->st.st_mtime < c1.mdate || path->st.st_mtime > c2.mdate)
346			goto crit_check_ret;
347	}
348
349	/* Check for creation date... */
350	if ((c1.rbitmap & (1<<DIRPBIT_CDATE))) {
351		c_date = path->st.st_mtime;
352		adp = adl_lkup(vol, path, adp);
353		if (adp && ad_getdate(adp, AD_DATE_CREATE, &ac_date) >= 0)
354		    c_date = AD_DATE_TO_UNIX(ac_date);
355
356		if (c_date < c1.cdate || c_date > c2.cdate)
357			goto crit_check_ret;
358	}
359
360	/* Check for backup date... */
361	if ((c1.rbitmap & (1<<DIRPBIT_BDATE))) {
362		b_date = path->st.st_mtime;
363		adp = adl_lkup(vol, path, adp);
364		if (adp && ad_getdate(adp, AD_DATE_BACKUP, &ab_date) >= 0)
365			b_date = AD_DATE_TO_UNIX(ab_date);
366
367		if (b_date < c1.bdate || b_date > c2.bdate)
368			goto crit_check_ret;
369	}
370
371	/* Check attributes */
372	if ((c1.rbitmap & (1<<DIRPBIT_ATTR)) && c2.attr != 0) {
373		if ((adp = adl_lkup(vol, path, adp))) {
374			ad_getattr(adp, &attr);
375			if ((attr & c2.attr) != c1.attr)
376				goto crit_check_ret;
377		} else
378			goto crit_check_ret;
379	}
380
381        /* Check file type ID */
382	if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.f_type != 0) {
383	    finfo = unpack_finderinfo(vol, path, &adp, &finderinfo,islnk);
384		if (finfo->f_type != c1.finfo.f_type)
385			goto crit_check_ret;
386	}
387
388	/* Check creator ID */
389	if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.creator != 0) {
390		if (!finfo) {
391			finfo = unpack_finderinfo(vol, path, &adp, &finderinfo,islnk);
392		}
393		if (finfo->creator != c1.finfo.creator)
394			goto crit_check_ret;
395	}
396
397	/* Check finder info attributes */
398	if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.attrs != 0) {
399		if (!finfo) {
400			finfo = unpack_finderinfo(vol, path, &adp, &finderinfo,islnk);
401		}
402
403		if ((finfo->attrs & c2.finfo.attrs) != c1.finfo.attrs)
404			goto crit_check_ret;
405	}
406
407	/* Check label */
408	if ((c1.rbitmap & (1<<DIRPBIT_FINFO)) && c2.finfo.label != 0) {
409		if (!finfo) {
410			finfo = unpack_finderinfo(vol, path, &adp, &finderinfo,islnk);
411		}
412		if ((finfo->label & c2.finfo.label) != c1.finfo.label)
413			goto crit_check_ret;
414	}
415	/* FIXME: Attributes check ! */
416
417	/* All criteria are met. */
418	result |= 1;
419crit_check_ret:
420	if (adp != NULL)
421		ad_close_metadata(adp);
422	return result;
423}
424
425/* ------------------------------ */
426static int rslt_add ( struct vol *vol, struct path *path, char **buf, int ext)
427{
428
429	char 		*p = *buf;
430	int 		ret;
431	size_t		tbuf =0;
432	u_int16_t	resultsize;
433	int 		isdir = S_ISDIR(path->st.st_mode);
434
435	/* Skip resultsize */
436	if (ext) {
437		p += sizeof(resultsize);
438	}
439	else {
440		p++;
441	}
442	*p++ = isdir ? FILDIRBIT_ISDIR : FILDIRBIT_ISFILE;    /* IsDir ? */
443
444	if (ext) {
445		*p++ = 0;                  /* Pad */
446	}
447
448	if ( isdir ) {
449        ret = getdirparams(vol, c1.dbitmap, path, path->d_dir, p , &tbuf );
450	}
451	else {
452	    /* FIXME slow if we need the file ID, we already know it, done ? */
453		ret = getfilparams ( vol, c1.fbitmap, path, path->d_dir, p, &tbuf);
454	}
455
456	if ( ret != AFP_OK )
457		return 0;
458
459	/* Make sure entry length is even */
460	if ((tbuf & 1)) {
461	   *p++ = 0;
462	   tbuf++;
463	}
464
465	if (ext) {
466		resultsize = htons(tbuf);
467		memcpy ( *buf, &resultsize, sizeof(resultsize) );
468		*buf += tbuf + 4;
469	}
470	else {
471		**buf = tbuf;
472		*buf += tbuf + 2;
473	}
474
475	return 1;
476}
477
478#define VETO_STR \
479        "./../.AppleDouble/.AppleDB/Network Trash Folder/TheVolumeSettingsFolder/TheFindByContentFolder/.AppleDesktop/.Parent/"
480
481/*!
482 * This function performs a filesystem search
483 *
484 * Uses globals c1, c2, the search criteria
485 *
486 * @param vol       (r)  volume we are searching on ...
487 * @param dir       (rw) directory we are starting from ...
488 * @param rmatches  (r)  maximum number of matches we can return
489 * @param pos       (r)  position we've stopped recently
490 * @param rbuf      (w)  output buffer
491 * @param nrecs     (w)  number of matches
492 * @param rsize     (w)  length of data written to output buffer
493 * @param ext       (r)  extended search flag
494 */
495#define NUM_ROUNDS 200
496static int catsearch(struct vol *vol,
497                     struct dir *dir,
498                     int rmatches,
499                     uint32_t *pos,
500                     char *rbuf,
501                     uint32_t *nrecs,
502                     int *rsize,
503                     int ext)
504{
505    static u_int32_t cur_pos;    /* Saved position index (ID) - used to remember "position" across FPCatSearch calls */
506    static DIR *dirpos; 		 /* UNIX structure describing currently opened directory. */
507    struct dir *curdir;          /* struct dir of current directory */
508	int cidx, r;
509	struct dirent *entry;
510	int result = AFP_OK;
511	int ccr;
512    struct path path;
513	char *vpath = vol->v_path;
514	char *rrbuf = rbuf;
515    time_t start_time;
516    int num_rounds = NUM_ROUNDS;
517    int cwd = -1;
518    int error;
519
520	if (*pos != 0 && *pos != cur_pos) {
521		result = AFPERR_CATCHNG;
522		goto catsearch_end;
523	}
524
525	/* FIXME: Category "offspring count ! */
526
527
528	/* We need to initialize all mandatory structures/variables and change working directory appropriate... */
529	if (*pos == 0) {
530		clearstack();
531		if (dirpos != NULL) {
532			closedir(dirpos);
533			dirpos = NULL;
534		}
535
536		if (addstack(vpath, dir, -1) == -1) {
537			result = AFPERR_MISC;
538			goto catsearch_end;
539		}
540		/* FIXME: Sometimes DID is given by client ! (correct this one above !) */
541	}
542
543	/* Save current path */
544    if ((cwd = open(".", O_RDONLY)) < 0) {
545        result = AFPERR_MISC;
546        goto catsearch_end;
547    }
548
549	/* So we are beginning... */
550    start_time = time(NULL);
551
552	while ((cidx = reducestack()) != -1) {
553		error = lchdir(dstack[cidx].path);
554
555		if (!error && dirpos == NULL)
556			dirpos = opendir(".");
557
558		if (dirpos == NULL)
559			dirpos = opendir(dstack[cidx].path);
560
561		if (error || dirpos == NULL) {
562			switch (errno) {
563			case EACCES:
564				dstack[cidx].checked = 1;
565				continue;
566			case EMFILE:
567			case ENFILE:
568			case ENOENT:
569				result = AFPERR_NFILE;
570				break;
571			case ENOMEM:
572			case ENOTDIR:
573			default:
574				result = AFPERR_MISC;
575			} /* switch (errno) */
576			goto catsearch_end;
577		}
578
579        if ((curdir = dirlookup_bypath(vol, dstack[cidx].path)) == NULL) {
580            result = AFPERR_MISC;
581            goto catsearch_end;
582        }
583
584		while ((entry = readdir(dirpos)) != NULL) {
585			(*pos)++;
586
587			if (!check_dirent(vol, entry->d_name))
588			   continue;
589
590			memset(&path, 0, sizeof(path));
591			path.u_name = entry->d_name;
592			if (of_stat(&path) != 0) {
593				switch (errno) {
594				case EACCES:
595				case ELOOP:
596				case ENOENT:
597					continue;
598				case ENOTDIR:
599				case EFAULT:
600				case ENOMEM:
601				case ENAMETOOLONG:
602				default:
603					result = AFPERR_MISC;
604					goto catsearch_end;
605				}
606			}
607			if (S_ISDIR(path.st.st_mode)) {
608				/* here we can short cut
609				   ie if in the same loop the parent dir wasn't in the cache
610				   ALL dirsearch_byname will fail.
611				*/
612                int unlen = strlen(path.u_name);
613                path.d_dir = dircache_search_by_name(vol,
614                                                     curdir,
615                                                     path.u_name,
616                                                     unlen);
617            	if (path.d_dir == NULL) {
618                	/* path.m_name is set by adddir */
619            	    if ((path.d_dir = dir_add(vol,
620                                              curdir,
621                                              &path,
622                                              unlen)) == NULL) {
623						result = AFPERR_MISC;
624						goto catsearch_end;
625					}
626                }
627                path.m_name = cfrombstr(path.d_dir->d_m_name);
628
629				if (addstack(path.u_name, path.d_dir, cidx) == -1) {
630					result = AFPERR_MISC;
631					goto catsearch_end;
632				}
633            } else {
634            	path.d_dir = curdir;
635            }
636
637			ccr = crit_check(vol, &path);
638
639			/* bit 0 means that criteria has been met */
640			if ((ccr & 1)) {
641				r = rslt_add ( vol, &path, &rrbuf, ext);
642
643				if (r == 0) {
644					result = AFPERR_MISC;
645					goto catsearch_end;
646				}
647				*nrecs += r;
648				/* Number of matches limit */
649				if (--rmatches == 0)
650					goto catsearch_pause;
651				/* Block size limit */
652				if (rrbuf - rbuf >= 448)
653					goto catsearch_pause;
654			}
655			/* MacOS 9 doesn't like servers executing commands longer than few seconds */
656			if (--num_rounds <= 0) {
657			    if (start_time != time(NULL)) {
658					result=AFP_OK;
659					goto catsearch_pause;
660			    }
661			    num_rounds = NUM_ROUNDS;
662			}
663		} /* while ((entry=readdir(dirpos)) != NULL) */
664		closedir(dirpos);
665		dirpos = NULL;
666		dstack[cidx].checked = 1;
667	} /* while (current_idx = reducestack()) != -1) */
668
669	/* We have finished traversing our tree. Return EOF here. */
670	result = AFPERR_EOF;
671	goto catsearch_end;
672
673catsearch_pause:
674	cur_pos = *pos;
675	save_cidx = cidx;
676
677catsearch_end: /* Exiting catsearch: error condition */
678	*rsize = rrbuf - rbuf;
679    if (cwd != -1) {
680        if ((fchdir(cwd)) != 0) {
681            LOG(log_debug, logtype_afpd, "error chdiring back: %s", strerror(errno));
682        }
683        close(cwd);
684    }
685	return result;
686} /* catsearch() */
687
688/*!
689 * This function performs a CNID db search
690 *
691 * Uses globals c1, c2, the search criteria
692 *
693 * @param vol       (r)  volume we are searching on ...
694 * @param dir       (rw) directory we are starting from ...
695 * @param uname     (r)  UNIX name of object to search
696 * @param rmatches  (r)  maximum number of matches we can return
697 * @param pos       (r)  position we've stopped recently
698 * @param rbuf      (w)  output buffer
699 * @param nrecs     (w)  number of matches
700 * @param rsize     (w)  length of data written to output buffer
701 * @param ext       (r)  extended search flag
702 */
703static int catsearch_db(struct vol *vol,
704                        struct dir *dir,
705                        const char *uname,
706                        int rmatches,
707                        uint32_t *pos,
708                        char *rbuf,
709                        uint32_t *nrecs,
710                        int *rsize,
711                        int ext)
712{
713    static char resbuf[DBD_MAX_SRCH_RSLTS * sizeof(cnid_t)];
714    static uint32_t cur_pos;
715    static int num_matches;
716    int ccr ,r;
717	int result = AFP_OK;
718    struct path path;
719	char *rrbuf = rbuf;
720    char buffer[MAXPATHLEN +2];
721    uint16_t flags = CONV_TOLOWER;
722
723    LOG(log_debug, logtype_afpd, "catsearch_db(req pos: %u): {pos: %u, name: %s}",
724        *pos, cur_pos, uname);
725
726	if (*pos != 0 && *pos != cur_pos) {
727		result = AFPERR_CATCHNG;
728		goto catsearch_end;
729	}
730
731    if (cur_pos == 0 || *pos == 0) {
732        if (convert_charset(vol->v_volcharset,
733                            vol->v_volcharset,
734                            vol->v_maccharset,
735                            uname,
736                            strlen(uname),
737                            buffer,
738                            MAXPATHLEN,
739                            &flags) == (size_t)-1) {
740            LOG(log_error, logtype_afpd, "catsearch_db: conversion error");
741            result = AFPERR_MISC;
742            goto catsearch_end;
743        }
744
745        LOG(log_debug, logtype_afpd, "catsearch_db: %s", buffer);
746
747        if ((num_matches = cnid_find(vol->v_cdb,
748                                     buffer,
749                                     strlen(uname),
750                                     resbuf,
751                                     sizeof(resbuf))) == -1) {
752            result = AFPERR_MISC;
753            goto catsearch_end;
754        }
755    }
756
757	while (cur_pos < num_matches) {
758        char *name;
759        cnid_t cnid, did;
760        char resolvebuf[12 + MAXPATHLEN + 1];
761        struct dir *dir;
762
763        /* Next CNID to process from buffer */
764        memcpy(&cnid, resbuf + cur_pos * sizeof(cnid_t), sizeof(cnid_t));
765        did = cnid;
766
767        if ((name = cnid_resolve(vol->v_cdb, &did, resolvebuf, 12 + MAXPATHLEN + 1)) == NULL)
768            goto next;
769        LOG(log_debug, logtype_afpd, "catsearch_db: {pos: %u, name:%s, cnid: %u}",
770            cur_pos, name, ntohl(cnid));
771        if ((dir = dirlookup(vol, did)) == NULL)
772            goto next;
773        if (movecwd(vol, dir) < 0 )
774            goto next;
775
776        memset(&path, 0, sizeof(path));
777        path.u_name = name;
778        path.m_name = utompath(vol, name, cnid, utf8_encoding());
779
780        if (of_stat(&path) != 0) {
781            switch (errno) {
782            case EACCES:
783            case ELOOP:
784                goto next;
785            case ENOENT:
786
787            default:
788                result = AFPERR_MISC;
789                goto catsearch_end;
790            }
791        }
792        /* For files path.d_dir is the parent dir, for dirs its the dir itself */
793        if (S_ISDIR(path.st.st_mode))
794            if ((dir = dirlookup(vol, cnid)) == NULL)
795                goto next;
796        path.d_dir = dir;
797
798        LOG(log_maxdebug, logtype_afpd,"catsearch_db: dir: %s, cwd: %s, name: %s",
799            cfrombstr(dir->d_fullpath), getcwdpath(), path.u_name);
800
801        /* At last we can check the search criteria */
802        ccr = crit_check(vol, &path);
803        if ((ccr & 1)) {
804            LOG(log_debug, logtype_afpd,"catsearch_db: match: %s/%s",
805                getcwdpath(), path.u_name);
806            /* bit 1 means that criteria has been met */
807            r = rslt_add(vol, &path, &rrbuf, ext);
808            if (r == 0) {
809                result = AFPERR_MISC;
810                goto catsearch_end;
811            }
812            *nrecs += r;
813            /* Number of matches limit */
814            if (--rmatches == 0)
815                goto catsearch_pause;
816            /* Block size limit */
817            if (rrbuf - rbuf >= 448)
818                goto catsearch_pause;
819        }
820    next:
821        cur_pos++;
822    } /* while */
823
824	/* finished */
825	result = AFPERR_EOF;
826    cur_pos = 0;
827	goto catsearch_end;
828
829catsearch_pause:
830    *pos = cur_pos;
831
832catsearch_end: /* Exiting catsearch: error condition */
833	*rsize = rrbuf - rbuf;
834    LOG(log_debug, logtype_afpd, "catsearch_db(req pos: %u): {pos: %u}", *pos, cur_pos);
835	return result;
836}
837
838/* -------------------------- */
839static int catsearch_afp(AFPObj *obj _U_, char *ibuf, size_t ibuflen,
840                  char *rbuf, size_t *rbuflen, int ext)
841{
842    struct vol *vol;
843    u_int16_t   vid;
844    u_int16_t   spec_len;
845    u_int32_t   rmatches, reserved;
846    u_int32_t	catpos[4];
847    u_int32_t   pdid = 0;
848    int ret, rsize;
849    u_int32_t nrecs = 0;
850    unsigned char *spec1, *spec2, *bspec1, *bspec2;
851    size_t	len;
852    u_int16_t	namelen;
853    u_int16_t	flags;
854    char  	    tmppath[256];
855    char        *uname;
856
857    *rbuflen = 0;
858
859    /* min header size */
860    if (ibuflen < 32) {
861        return AFPERR_PARAM;
862    }
863
864    memset(&c1, 0, sizeof(c1));
865    memset(&c2, 0, sizeof(c2));
866
867    ibuf += 2;
868    memcpy(&vid, ibuf, sizeof(vid));
869    ibuf += sizeof(vid);
870
871    if ((vol = getvolbyvid(vid)) == NULL) {
872        return AFPERR_PARAM;
873    }
874
875    memcpy(&rmatches, ibuf, sizeof(rmatches));
876    rmatches = ntohl(rmatches);
877    ibuf += sizeof(rmatches);
878
879    /* FIXME: (rl) should we check if reserved == 0 ? */
880    ibuf += sizeof(reserved);
881
882    memcpy(catpos, ibuf, sizeof(catpos));
883    ibuf += sizeof(catpos);
884
885    memcpy(&c1.fbitmap, ibuf, sizeof(c1.fbitmap));
886    c1.fbitmap = c2.fbitmap = ntohs(c1.fbitmap);
887    ibuf += sizeof(c1.fbitmap);
888
889    memcpy(&c1.dbitmap, ibuf, sizeof(c1.dbitmap));
890    c1.dbitmap = c2.dbitmap = ntohs(c1.dbitmap);
891    ibuf += sizeof(c1.dbitmap);
892
893    memcpy(&c1.rbitmap, ibuf, sizeof(c1.rbitmap));
894    c1.rbitmap = c2.rbitmap = ntohl(c1.rbitmap);
895    ibuf += sizeof(c1.rbitmap);
896
897    if (! (c1.fbitmap || c1.dbitmap)) {
898	    return AFPERR_BITMAP;
899    }
900
901    if ( ext) {
902        memcpy(&spec_len, ibuf, sizeof(spec_len));
903        spec_len = ntohs(spec_len);
904    }
905    else {
906        /* with catsearch only name and parent id are allowed */
907    	c1.fbitmap &= (1<<FILPBIT_LNAME) | (1<<FILPBIT_PDID);
908    	c1.dbitmap &= (1<<DIRPBIT_LNAME) | (1<<DIRPBIT_PDID);
909        spec_len = *(unsigned char*)ibuf;
910    }
911
912    /* Parse file specifications */
913    spec1 = (unsigned char*)ibuf;
914    spec2 = (unsigned char*)ibuf + spec_len + 2;
915
916    spec1 += 2;
917    spec2 += 2;
918
919    bspec1 = spec1;
920    bspec2 = spec2;
921    /* File attribute bits... */
922    if (c1.rbitmap & (1 << FILPBIT_ATTR)) {
923	    memcpy(&c1.attr, spec1, sizeof(c1.attr));
924	    spec1 += sizeof(c1.attr);
925	    memcpy(&c2.attr, spec2, sizeof(c2.attr));
926	    spec2 += sizeof(c1.attr);
927    }
928
929    /* Parent DID */
930    if (c1.rbitmap & (1 << FILPBIT_PDID)) {
931            memcpy(&c1.pdid, spec1, sizeof(pdid));
932	    spec1 += sizeof(c1.pdid);
933	    memcpy(&c2.pdid, spec2, sizeof(pdid));
934	    spec2 += sizeof(c2.pdid);
935    } /* FIXME: PDID - do we demarshall this argument ? */
936
937    /* Creation date */
938    if (c1.rbitmap & (1 << FILPBIT_CDATE)) {
939	    memcpy(&c1.cdate, spec1, sizeof(c1.cdate));
940	    spec1 += sizeof(c1.cdate);
941	    c1.cdate = AD_DATE_TO_UNIX(c1.cdate);
942	    memcpy(&c2.cdate, spec2, sizeof(c2.cdate));
943	    spec2 += sizeof(c1.cdate);
944	    ibuf += sizeof(c1.cdate);;
945	    c2.cdate = AD_DATE_TO_UNIX(c2.cdate);
946    }
947
948    /* Modification date */
949    if (c1.rbitmap & (1 << FILPBIT_MDATE)) {
950	    memcpy(&c1.mdate, spec1, sizeof(c1.mdate));
951	    c1.mdate = AD_DATE_TO_UNIX(c1.mdate);
952	    spec1 += sizeof(c1.mdate);
953	    memcpy(&c2.mdate, spec2, sizeof(c2.mdate));
954	    c2.mdate = AD_DATE_TO_UNIX(c2.mdate);
955	    spec2 += sizeof(c1.mdate);
956    }
957
958    /* Backup date */
959    if (c1.rbitmap & (1 << FILPBIT_BDATE)) {
960	    memcpy(&c1.bdate, spec1, sizeof(c1.bdate));
961	    spec1 += sizeof(c1.bdate);
962	    c1.bdate = AD_DATE_TO_UNIX(c1.bdate);
963	    memcpy(&c2.bdate, spec2, sizeof(c2.bdate));
964	    spec2 += sizeof(c2.bdate);
965	    c1.bdate = AD_DATE_TO_UNIX(c2.bdate);
966    }
967
968    /* Finder info */
969    if (c1.rbitmap & (1 << FILPBIT_FINFO)) {
970    	packed_finder buf;
971
972	    memcpy(buf, spec1, sizeof(buf));
973	    unpack_buffer(&c1.finfo, buf);
974	    spec1 += sizeof(buf);
975
976	    memcpy(buf, spec2, sizeof(buf));
977	    unpack_buffer(&c2.finfo, buf);
978	    spec2 += sizeof(buf);
979    } /* Finder info */
980
981    if ((c1.rbitmap & (1 << DIRPBIT_OFFCNT)) != 0) {
982        /* Offspring count - only directories */
983		if (c1.fbitmap == 0) {
984	    	memcpy(&c1.offcnt, spec1, sizeof(c1.offcnt));
985	    	spec1 += sizeof(c1.offcnt);
986	    	c1.offcnt = ntohs(c1.offcnt);
987	    	memcpy(&c2.offcnt, spec2, sizeof(c2.offcnt));
988	    	spec2 += sizeof(c2.offcnt);
989	    	c2.offcnt = ntohs(c2.offcnt);
990		}
991		else if (c1.dbitmap == 0) {
992			/* ressource fork length */
993		}
994		else {
995	    	return AFPERR_BITMAP;  /* error */
996		}
997    } /* Offspring count/ressource fork length */
998
999    /* Long name */
1000    if (c1.rbitmap & (1 << FILPBIT_LNAME)) {
1001        /* Get the long filename */
1002		memcpy(tmppath, bspec1 + spec1[1] + 1, (bspec1 + spec1[1])[0]);
1003		tmppath[(bspec1 + spec1[1])[0]]= 0;
1004		len = convert_string ( vol->v_maccharset, CH_UCS2, tmppath, -1, c1.lname, sizeof(c1.lname));
1005        if (len == (size_t)(-1))
1006            return AFPERR_PARAM;
1007
1008#if 0
1009		/* FIXME: do we need it ? It's always null ! */
1010		memcpy(c2.lname, bspec2 + spec2[1] + 1, (bspec2 + spec2[1])[0]);
1011		c2.lname[(bspec2 + spec2[1])[0]]= 0;
1012#endif
1013    }
1014        /* UTF8 Name */
1015    if (c1.rbitmap & (1 << FILPBIT_PDINFO)) {
1016
1017		/* offset */
1018		memcpy(&namelen, spec1, sizeof(namelen));
1019		namelen = ntohs (namelen);
1020
1021		spec1 = bspec1+namelen+4; /* Skip Unicode Hint */
1022
1023		/* length */
1024		memcpy(&namelen, spec1, sizeof(namelen));
1025		namelen = ntohs (namelen);
1026		if (namelen > UTF8FILELEN_EARLY)  /* Safeguard */
1027			namelen = UTF8FILELEN_EARLY;
1028
1029		memcpy (c1.utf8name, spec1+2, namelen);
1030		c1.utf8name[namelen] = 0;
1031        if ((uname = mtoupath(vol, c1.utf8name, 0, utf8_encoding())) == NULL)
1032            return AFPERR_PARAM;
1033
1034 		/* convert charset */
1035		flags = CONV_PRECOMPOSE;
1036 		len = convert_charset(CH_UTF8_MAC, CH_UCS2, CH_UTF8, c1.utf8name, namelen, c1.utf8name, 512, &flags);
1037        if (len == (size_t)(-1))
1038            return AFPERR_PARAM;
1039    }
1040
1041    /* Call search */
1042    *rbuflen = 24;
1043    if ((c1.rbitmap & (1 << FILPBIT_PDINFO))
1044        && (strcmp(vol->v_cnidscheme, "dbd") == 0)
1045        && (vol->v_flags & AFPVOL_SEARCHDB))
1046        /* we've got a name and it's a dbd volume, so search CNID database */
1047        ret = catsearch_db(vol, vol->v_root, uname, rmatches, &catpos[0], rbuf+24, &nrecs, &rsize, ext);
1048    else
1049        /* perform a slow filesystem tree search */
1050        ret = catsearch(vol, vol->v_root, rmatches, &catpos[0], rbuf+24, &nrecs, &rsize, ext);
1051
1052    memcpy(rbuf, catpos, sizeof(catpos));
1053    rbuf += sizeof(catpos);
1054
1055    c1.fbitmap = htons(c1.fbitmap);
1056    memcpy(rbuf, &c1.fbitmap, sizeof(c1.fbitmap));
1057    rbuf += sizeof(c1.fbitmap);
1058
1059    c1.dbitmap = htons(c1.dbitmap);
1060    memcpy(rbuf, &c1.dbitmap, sizeof(c1.dbitmap));
1061    rbuf += sizeof(c1.dbitmap);
1062
1063    nrecs = htonl(nrecs);
1064    memcpy(rbuf, &nrecs, sizeof(nrecs));
1065    rbuf += sizeof(nrecs);
1066    *rbuflen += rsize;
1067
1068    return ret;
1069} /* catsearch_afp */
1070
1071/* -------------------------- */
1072int afp_catsearch (AFPObj *obj, char *ibuf, size_t ibuflen,
1073                  char *rbuf, size_t *rbuflen)
1074{
1075	return catsearch_afp( obj, ibuf, ibuflen, rbuf, rbuflen, 0);
1076}
1077
1078
1079int afp_catsearch_ext (AFPObj *obj, char *ibuf, size_t ibuflen,
1080                  char *rbuf, size_t *rbuflen)
1081{
1082	return catsearch_afp( obj, ibuf, ibuflen, rbuf, rbuflen, 1);
1083}
1084
1085/* FIXME: we need a clean separation between afp stubs and 'real' implementation */
1086/* (so, all buffer packing/unpacking should be done in stub, everything else
1087   should be done in other functions) */
1088