1/*
2 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include "webdavd.h"
25
26#include <sys/syslog.h>
27#include <sys/errno.h>
28#include <stdlib.h>
29#include <strings.h>
30#include <stdio.h>
31#include <time.h>
32#include <unistd.h>
33#include <pthread.h>
34
35#include "webdav_cache.h"
36#include "webdav_parse.h"
37#include "OpaqueIDs.h"
38#include "LogMessage.h"
39
40/*****************************************************************************/
41
42static int open_cache_files = 0;
43
44pthread_mutex_t g_node_cache_lock;
45
46/*****************************************************************************/
47
48/*
49 * The root node_entry.
50 * The rest of the node_entry entries will be in the tree below the root node_entry.
51 */
52struct node_entry *g_root_node;
53
54/*
55 * The deleted root node_entry.
56 * When an open file is deleted (including when something is renamed over an
57 * existing node), it will be removed from the main tree and inserted as a child
58 * of this "fake" node_entry. URLs cannot be created for children of this node
59 * (but they should never need to talk to the server anyway -- they are deleted)
60 * Nodes are freed (using nodecache_free_nodes()) only when they are children of this node.
61 */
62struct node_entry *g_deleted_root_node;
63
64/*
65 * The next fileid number to be assigned.
66*/
67u_int32_t g_next_fileid;
68
69/*
70 * The file_cache_head list.
71 * Entries are stored in the order inserted into the list.
72 */
73struct node_head g_file_list;
74
75/* static prototypes */
76
77static int internal_add_attributes(
78	struct node_entry *node,
79	uid_t uid,
80	struct webdav_stat_attr *statp,
81	char *appledoubleheader);
82static int internal_remove_attributes(
83	struct node_entry *node,
84	int remove_appledoubleheader);
85static int internal_node_appledoubleheader_valid(
86	struct node_entry *node,
87	uid_t uid);
88static void invalidate_level(
89	struct node_entry *dir_node);
90static void internal_remove_file_cache(
91	struct node_entry *node);
92static int internal_add_file_cache(
93	struct node_entry *node,
94	int fd);
95static int internal_get_node(
96	struct node_entry *parent,
97	size_t name_length,
98	const char *name,
99	int make_entry,
100	int client_created,
101	webdav_filetype_t node_type,
102	struct node_entry **node);
103static void internal_free_nodes(void);
104static int internal_move_node(
105	struct node_entry *node,
106	struct node_entry *new_parent,
107	size_t new_name_length,
108	char *new_name);
109static int delete_node_tree(
110	struct node_entry *node,
111	int recursive);
112static int internal_invalidate_directory_node_time(
113	struct node_entry *dir_node);
114static int internal_delete_invalid_directory_nodes(
115	struct node_entry *dir_node);
116static int internal_get_path_from_node(
117	struct node_entry *target_node,
118	bool *pathHasRedirection,
119	char **path);
120static CFArrayRef internal_get_locktokens(
121	struct node_entry *a_node);
122
123static int init_node_cache_lock(void);
124
125/*****************************************************************************/
126
127/* this adds or replaces attribute information to node */
128static int internal_add_attributes(
129	struct node_entry *node,		/* the node_entry to update or add attributes_entry to */
130	uid_t uid,						/* the uid these attributes are valid for */
131	struct webdav_stat_attr *statp,	/* the stat buffer */
132	char *appledoubleheader)	/* pointer appledoubleheader or NULL */
133{
134	int error;
135	time_t current_time;
136
137	error = 0;
138
139	/* first, clear out any old attributes */
140	(void)internal_remove_attributes(node, (appledoubleheader != NULL));
141
142	current_time = time(NULL);
143	require_action(current_time != -1, time, error = errno);
144	/* add appledoubleheader first since it could fail */
145	if ( appledoubleheader != NULL )
146	{
147		node->attr_appledoubleheader = malloc(APPLEDOUBLEHEADER_LENGTH);
148		require_action(node->attr_appledoubleheader != NULL, malloc_attr_appledoubleheader, error = ENOMEM);
149
150		memcpy(node->attr_appledoubleheader, appledoubleheader, APPLEDOUBLEHEADER_LENGTH);
151		node->attr_appledoubleheader_time = current_time;
152	}
153	/* fill in the rest of the fields */
154	node->attr_uid = uid;
155	node->attr_time = current_time;
156	memcpy(&(node->attr_stat_info), statp, sizeof(struct webdav_stat_attr));
157
158malloc_attr_appledoubleheader:
159time:
160
161	return ( error );
162}
163
164/*****************************************************************************/
165
166int nodecache_add_attributes(
167	struct node_entry *node,		/* the node_entry to update or add attributes_entry to */
168	uid_t uid,						/* the uid these attributes are valid for */
169	struct webdav_stat_attr *statp,	/* the stat buffer */
170	char *appledoubleheader)	/* pointer appledoubleheader or NULL */
171{
172	int error;
173
174	lock_node_cache();
175
176	error = internal_add_attributes(node, uid, statp, appledoubleheader);
177
178	unlock_node_cache();
179
180	return ( error );
181}
182
183/*****************************************************************************/
184
185/* clear out the attribute fields and release any memory */
186static int internal_remove_attributes(
187	struct node_entry *node,
188	int remove_appledoubleheader)
189{
190	node->attr_uid = 0;
191	node->attr_time = 0;
192	node->attr_stat_info.attr_create_time.tv_sec = 0;
193	memset(&node->attr_stat_info, 0, sizeof(struct webdav_stat_attr));
194	if ( remove_appledoubleheader && (node->attr_appledoubleheader != NULL) )
195	{
196		free(node->attr_appledoubleheader);
197		node->attr_appledoubleheader = NULL;
198		node->attr_appledoubleheader_time = 0;
199	}
200	return ( 0 );
201}
202
203/*****************************************************************************/
204
205int nodecache_remove_attributes(
206	struct node_entry *node)	/* the node to remove attributes from */
207{
208	int error;
209
210	lock_node_cache();
211
212	error = internal_remove_attributes(node, TRUE);
213
214	unlock_node_cache();
215
216	return ( error );
217}
218
219/*****************************************************************************/
220
221static int internal_node_appledoubleheader_valid(
222	struct node_entry *node,
223	uid_t uid)
224{
225	int result;
226
227	result = ( (node->attr_appledoubleheader != NULL) && /* is there attr_appledoubleheader? */
228			 (node->attr_appledoubleheader_time != 0) && /* 0 attr_appledoubleheader_time is invalid */
229			 ((uid == node->attr_uid) || (0 == node->attr_uid)) && /* does this user or root have access to the cached attributes? */
230			 (time(NULL) < (node->attr_appledoubleheader_time + FILE_VALIDATION_TIMEOUT)) ); /* don't cache them too long */
231
232	return ( result );
233}
234
235/*****************************************************************************/
236
237int node_appledoubleheader_valid(
238	struct node_entry *node,
239	uid_t uid)
240{
241	int result;
242
243	lock_node_cache();
244
245	result = internal_node_appledoubleheader_valid(node, uid);
246
247	unlock_node_cache();
248
249	return ( result );
250}
251
252/*****************************************************************************/
253
254int node_attributes_valid(
255	struct node_entry *node,
256	uid_t uid)
257{
258	int result;
259
260	lock_node_cache();
261
262	result = ( (node->attr_time != 0) && /* 0 attr_time is invalid */
263			 ((uid == node->attr_uid) || (0 == node->attr_uid)) && /* does this user or root have access to the cached attributes */
264			 (time(NULL) < (node->attr_time + ATTRIBUTES_TIMEOUT_MAX)) ); /* don't cache them too long */
265
266	unlock_node_cache();
267
268	return ( result );
269
270#if 0 /* FUTURE */
271	int result;
272
273	/* are the cached attributes possibly valid and does this user or root have access to them? */
274	if ( (node->attr_time != 0) && ((uid == node->attr_uid) || (0 == node->attr_uid)) )
275	{
276		/*
277		 * Determine attribute_time_out. It will be something between
278		 * ATTRIBUTES_TIMEOUT_MIN and ATTRIBUTES_TIMEOUT_MAX where recently
279		 * modified files have a short timeout and files that haven't been
280		 * modified in a long time have a long timeout. This is the same
281		 * algorithm used by NFS (with different min and max values).
282		 */
283		time_t current_time;
284		time_t attribute_time_out;
285
286		current_time = time(NULL);
287		attribute_time_out = (current_time - node->attr_stat.st_mtimespec.tv_sec) / 10;
288		if (attribute_time_out < ATTRIBUTES_TIMEOUT_MIN)
289		{
290			attribute_time_out = ATTRIBUTES_TIMEOUT_MIN;
291		}
292		else if (attribute_time_out > ATTRIBUTES_TIMEOUT_MAX)
293		{
294			attribute_time_out = ATTRIBUTES_TIMEOUT_MAX;
295		}
296		/* has too much time passed? */
297		result = ((current_time - node->attr_time) <= attribute_time_out);
298	}
299	else
300	{
301		result = FALSE;
302	}
303	return ( result );
304#endif /* FUTURE */
305}
306
307/*****************************************************************************/
308
309/* invalidate the node attribute and file cache caches for dir_node's children */
310static void invalidate_level(struct node_entry *dir_node)
311{
312	struct node_entry *node;
313
314	/* invalidate each child node */
315	LIST_FOREACH(node, &(dir_node->children), entries)
316	{
317		node->attr_time = 0;
318		node->attr_stat_info.attr_create_time.tv_sec = -1;
319		node->file_validated_time = 0;
320		/* invalidate this node's children (if any) */
321		invalidate_level(node);
322	}
323}
324
325/* filesystem_invalidate_caches will call this to do all the work */
326void nodecache_invalidate_caches(void)
327{
328	struct node_entry *node;
329
330	lock_node_cache();
331
332	node = g_root_node;
333	node->attr_time = 0;
334	node->attr_stat_info.attr_create_time.tv_sec = -1;
335	node->file_validated_time = 0;
336	/* invalidate this node's children (if any) */
337	invalidate_level(node);
338
339	unlock_node_cache();
340}
341
342/*****************************************************************************/
343
344#if 0
345
346static int gtabs;
347
348static void display_node_tree_level(struct node_entry *dir_node)
349{
350	struct node_entry *node;
351
352	LIST_FOREACH(node, &(dir_node->children), entries)
353	{
354		++gtabs;
355		syslog(LOG_ERR, "%*s%s: %ld %s, node ino %ld, attr ino %u", gtabs*3, "", node->name, (unsigned long)node, node->node_type == WEBDAV_DIR_TYPE ? "d" : "f", node->fileid, node->attr_stat.st_ino);
356		display_node_tree_level(node);
357		--gtabs;
358	}
359}
360
361void nodecache_display_node_tree(void)
362{
363	struct node_entry *node;
364
365	lock_node_cache();
366
367	node = g_root_node;
368	gtabs = 0;
369	syslog(LOG_ERR, "%*s%s: %ld %s, node ino %ld, attr ino %u", gtabs*3, "", node->name, (unsigned long)node, node->node_type == WEBDAV_DIR_TYPE ? "d" : "f", node->fileid, node->attr_stat.st_ino);
370	display_node_tree_level(node);
371	syslog(LOG_ERR, "-----");
372
373	unlock_node_cache();
374}
375
376void nodecache_display_file_cache(void)
377{
378	struct node_entry *node;
379	unsigned long count;
380
381	count = 0;
382
383	lock_node_cache();
384
385	LIST_FOREACH(node, &g_file_list, file_list)
386	{
387		++count;
388
389		if ( !NODE_IS_DELETED(node) )
390		{
391			syslog(LOG_ERR, "fd: %d, ino %ld %s%s %s",
392				node->file_fd,
393				node->fileid,
394				node->node_type == WEBDAV_DIR_TYPE ? "d" : "f",
395				NODE_FILE_IS_OPEN(node) ? "+ " : "- ",
396				node->name);
397		}
398		else
399		{
400			syslog(LOG_ERR, "fd: %d, ino %ld %s%s %s",
401				node->file_fd,
402				node->fileid,
403				node->node_type == WEBDAV_DIR_TYPE ? "d" : "f",
404				NODE_FILE_IS_OPEN(node) ? "+X" : "-X",
405				node->name);
406		}
407	}
408	syslog(LOG_ERR, "----- %ld -----", count);
409
410	unlock_node_cache();
411}
412
413#endif
414
415/*****************************************************************************/
416
417static struct node_entry *g_next_file_cache_node = NULL;
418
419struct node_entry *nodecache_get_next_file_cache_node(
420	int get_first)					/* if true, return first file cache node; otherwise, the next one */
421{
422	struct node_entry *node;
423
424	lock_node_cache();
425
426	if ( get_first )
427	{
428		node = g_file_list.lh_first;
429	}
430	else
431	{
432		node = g_next_file_cache_node;
433	}
434
435	if ( node != NULL )
436	{
437		g_next_file_cache_node = node->file_list.le_next;
438	}
439	else
440	{
441		g_next_file_cache_node = NULL;
442	}
443
444	unlock_node_cache();
445
446	return ( node );
447}
448
449/*****************************************************************************/
450
451static int internal_add_file_cache(
452	struct node_entry *node,		/* the node_entry to add a file_cache_entry to */
453	int fd)							/* the file descriptor of the cache file */
454{
455	int error;
456
457	error = 0;
458
459	/* only add it if it's not already cached */
460	require_quiet(!NODE_FILE_IS_CACHED(node), already_cached);
461
462	while ( open_cache_files >= WEBDAV_MAX_OPEN_FILES )
463	{
464		struct node_entry *file_node;
465		struct node_entry *victim_node;
466
467		/* find oldest victim node */
468		victim_node = NULL;
469		LIST_FOREACH(file_node, &g_file_list, file_list)
470		{
471			if ( !NODE_FILE_IS_OPEN(file_node) )
472			{
473				victim_node = file_node;
474			}
475		}
476
477		require_action(victim_node != NULL, too_many_files_open, error = ENFILE);
478
479		internal_remove_file_cache(victim_node);
480	}
481
482	++open_cache_files;
483	node->flags |= nodeInFileListMask;
484	node->file_fd = fd;
485	node->file_status = WEBDAV_DOWNLOAD_NEVER;
486	node->file_validated_time = 0;
487	node->file_inactive_time = 0;
488	node->file_last_modified = -1;
489	if ( node->file_entity_tag != NULL )
490	{
491		free(node->file_entity_tag);
492		node->file_entity_tag = NULL;
493	}
494	node->file_locktoken_uid = 0;
495	if ( node->file_locktoken != NULL )
496	{
497		free(node->file_locktoken);
498		node->file_locktoken = NULL;
499	}
500
501	/* add it to the head of the g_file_active_list */
502	LIST_INSERT_HEAD(&g_file_list, node, file_list);
503
504too_many_files_open:
505already_cached:
506
507	return ( error );
508}
509
510/*****************************************************************************/
511
512int nodecache_add_file_cache(
513	struct node_entry *node,		/* the node_entry to add a file_cache_entry to */
514	int fd)							/* the file descriptor of the cache file */
515{
516	int error;
517
518	lock_node_cache();
519
520	error = internal_add_file_cache(node, fd);
521
522	unlock_node_cache();
523
524	return ( error );
525}
526
527/*****************************************************************************/
528
529static void internal_remove_file_cache(struct node_entry *node)
530{
531	if ( NODE_FILE_IS_CACHED(node) )
532	{
533		if (open_cache_files > 0)
534		{
535			--open_cache_files;
536		}
537		else
538		{
539			debug_string("internal_remove_file_cache: open_cache_files was zero");
540		}
541		node->flags &= ~nodeInFileListMask;
542		close(node->file_fd);
543		node->file_fd = -1;
544		if ( node == g_next_file_cache_node )
545		{
546			g_next_file_cache_node = node->file_list.le_next;
547		}
548		/* remove from g_file_list */
549		LIST_REMOVE(node, file_list);
550		node->file_list.le_next = NULL;
551		node->file_list.le_prev = NULL;
552		node->file_status = WEBDAV_DOWNLOAD_NEVER;
553		node->file_validated_time = 0;
554		node->file_inactive_time = 0;
555		node->file_last_modified = -1;
556		if ( node->file_entity_tag != NULL )
557		{
558			free(node->file_entity_tag);
559			node->file_entity_tag = NULL;
560		}
561		node->file_locktoken_uid = 0;
562		if ( node->file_locktoken != NULL )
563		{
564			free(node->file_locktoken);
565			node->file_locktoken = NULL;
566		}
567	}
568}
569
570/*****************************************************************************/
571
572void nodecache_remove_file_cache(struct node_entry *node)
573{
574	lock_node_cache();
575
576	internal_remove_file_cache(node);
577
578	unlock_node_cache();
579}
580
581/*****************************************************************************/
582
583/* called at mount time to initialize */
584int nodecache_init(
585	size_t name_length,				/* length of root node name */
586	char *name,						/* the utf8 name of root node */
587	struct node_entry **root_node)	/* the root node */
588{
589	int error;
590
591	error = 0;
592
593	g_next_fileid = WEBDAV_ROOTFILEID;
594
595	/* allocate space for g_root_node and its name */
596	g_root_node = calloc(1, sizeof(struct node_entry));
597	require_action(g_root_node != NULL, calloc_g_root_node, error = ENOMEM);
598
599	*root_node = g_root_node;
600	g_root_node->name = malloc(name_length + 1);
601	require_action(g_root_node->name != NULL, malloc_name, error = ENOMEM);
602
603	/* initialize the node_entry */
604	g_root_node->parent = NULL;
605	LIST_INIT(&g_root_node->children);
606	g_root_node->name_length = name_length;
607	memcpy(g_root_node->name, name, name_length);
608	g_root_node->name[name_length] = '\0';
609	g_root_node->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, g_root_node->name, kCFStringEncodingUTF8, kCFAllocatorNull);
610	g_root_node->fileid = g_next_fileid++;
611	g_root_node->node_type = WEBDAV_DIR_TYPE;
612	g_root_node->node_time = time(NULL);
613	error = AssignOpaqueID(g_root_node, &g_root_node->nodeid);
614	require_noerr(error, AssignOpaqueID);
615
616	/* attribute fields are already zeroed */
617	/* file cache fields are already zeroed - set file_fd to indicate there is no cache file */
618	g_root_node->file_fd = -1;
619
620	/* allocate space for g_deleted_root_node and its empty name */
621	g_deleted_root_node = calloc(1, sizeof(struct node_entry));
622	require_action(g_root_node != NULL, calloc_g_deleted_root_node, error = ENOMEM);
623
624	g_deleted_root_node->name = malloc(1);
625	require_action(g_root_node->name != NULL, malloc_g_deleted_root_node_name, error = ENOMEM);
626
627	/* initialize the node_entry */
628	g_deleted_root_node->parent = NULL;
629	LIST_INIT(&g_deleted_root_node->children);
630	g_deleted_root_node->name_length = 0;
631	g_deleted_root_node->name[0] = '\0';
632	g_deleted_root_node->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, g_deleted_root_node->name, kCFStringEncodingUTF8, kCFAllocatorNull);
633	/* attribute fields are already zeroed */
634	/* file cache fields are already zeroed - set file_fd to indicate there is no cache file */
635	g_deleted_root_node->file_fd = -1;
636
637	/* initialize g_file_list header */
638	LIST_INIT(&g_file_list);
639
640	error = init_node_cache_lock();
641
642malloc_g_deleted_root_node_name:
643calloc_g_deleted_root_node:
644malloc_name:
645AssignOpaqueID:
646calloc_g_root_node:
647
648	return ( error );
649}
650
651/*****************************************************************************/
652
653/*
654 * Finds a node by name in the parent node's children. If the node is
655 * not found and make_entry is TRUE, then internal_get_node creates a new node.
656 */
657static int internal_get_node(
658	struct node_entry *parent,		/* the parent node_entry */
659	size_t name_length,				/* length of name */
660	const char *name,				/* the utf8 name of the node */
661	int make_entry,					/* TRUE if a new node_entry should be created if needed */
662	int client_created,				/* TRUE if this client created the node (used in conjunction with make_entry */
663	webdav_filetype_t node_type,	/* if make_entry is TRUE, the type of node to create */
664	struct node_entry **node)		/* the found (or new) node_entry */
665{
666	struct node_entry *node_ptr;
667	int error;
668
669	node_ptr = NULL;
670	error = 0;
671
672	if ( name_length != 0 && name != NULL )
673	{
674		CFStringRef name_string;
675
676		name_string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)name, name_length, kCFStringEncodingUTF8, false);
677		require_action(name_string != NULL, out, error = EINVAL);
678
679		/* search for an existing node_entry */
680		LIST_FOREACH(node_ptr, &(parent->children), entries)
681		{
682			if ( CFStringCompare(name_string, node_ptr->name_ref, kCFCompareNonliteral) == kCFCompareEqualTo )
683			{
684				break;
685			}
686		}
687
688		CFRelease(name_string);
689	}
690	else
691	{
692		node_ptr = parent;
693	}
694
695	/* if we didn't find it and we're supposed to create it... */
696	if ( node_ptr == NULL )
697	{
698		if ( make_entry )
699		{
700			/* allocate new node_entry */
701			node_ptr = calloc(1, sizeof(struct node_entry));
702			require_action(node_ptr != NULL, calloc_node_ptr, error = ENOMEM; webdav_kill(-1));
703
704			/* allocate space for its name */
705			node_ptr->name = malloc(name_length + 1);
706			require_action(node_ptr->name != NULL, malloc_name, node_ptr = NULL; error = ENOMEM; webdav_kill(-1));
707
708			/* initialize the node_entry */
709			node_ptr->parent = parent;
710			LIST_INIT(&node_ptr->children);
711			node_ptr->name_length = name_length;
712			memcpy(node_ptr->name, name, name_length);
713			node_ptr->name[name_length] = '\0';
714			node_ptr->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, node_ptr->name, kCFStringEncodingUTF8, kCFAllocatorNull);
715			node_ptr->fileid = g_next_fileid++;
716			node_ptr->node_type = node_type;
717			node_ptr->node_time = time(NULL);
718			node_ptr->attr_stat_info.attr_create_time.tv_sec = 0;
719			node_ptr->isRedirected = false;
720			if ( client_created )
721			{
722				node_ptr->flags |= nodeRecentMask;
723			}
724			error = AssignOpaqueID(node_ptr, &node_ptr->nodeid);
725			require_noerr_action(error, AssignOpaqueID, node_ptr = NULL; webdav_kill(-1));
726
727			/* attribute fields are already zeroed */
728			/* file cache fields are already zeroed - set file_fd to indicate there is no cache file */
729			node_ptr->file_fd = -1;
730
731			/* insert the node_entry into the parent's children list */
732			LIST_INSERT_HEAD(&parent->children, node_ptr, entries);
733		}
734		else
735		{
736			error = ENOENT;
737		}
738	}
739	else
740	{
741		/* update node_time of existing node */
742		if ( make_entry )
743		{
744			node_ptr->node_time = time(NULL);
745		}
746		/* set or clear the recent flag */
747		if ( client_created )
748		{
749			node_ptr->flags |= nodeRecentMask;
750		}
751		else
752		{
753			node_ptr->flags &= ~nodeRecentMask;
754		}
755	}
756
757AssignOpaqueID:
758malloc_name:
759calloc_node_ptr:
760out:
761
762	/* return node_entry */
763	if ( error == 0 )
764	{
765		*node = node_ptr;
766	}
767	else
768	{
769		*node = NULL;
770	}
771
772	return ( error );
773}
774
775/*****************************************************************************/
776
777int nodecache_get_node(
778	struct node_entry *parent,		/* the parent node_entry */
779	size_t name_length,				/* length of name */
780	const char *name,				/* the utf8 name of the node */
781	int make_entry,					/* TRUE if a new node_entry should be created if needed*/
782	int client_created,				/* TRUE if this client created the node (used in conjunction with make_entry */
783	webdav_filetype_t node_type,	/* if make_entry is TRUE, the type of node to create */
784	struct node_entry **node)		/* the found (or new) node */
785{
786	int error;
787
788	lock_node_cache();
789
790	error = internal_get_node(parent, name_length, name, make_entry, client_created, node_type, node);
791
792	unlock_node_cache();
793
794	return ( error );
795}
796
797/*****************************************************************************/
798
799/*
800 * internal_free_nodes
801 * This function frees uncached nodes on the deleted list.
802 */
803static void internal_free_nodes(void)
804{
805	struct node_entry *node;
806
807	node = g_deleted_root_node->children.lh_first;
808	while ( node != NULL )
809	{
810		struct node_entry *next_node;
811
812		next_node = node->entries.le_next;
813		/* free this node ONLY if it's  NOT on the file list */
814		if ( !NODE_FILE_IS_CACHED(node) )
815		{
816			/* remove the node_entry from the list it is in */
817			LIST_REMOVE(node, entries);
818
819			/* invalidate the nodeid */
820			(void) DeleteOpaqueID(node->nodeid);
821			node->nodeid = kInvalidOpaqueID;
822
823			if ( node->file_entity_tag != NULL )
824			{
825				free(node->file_entity_tag);
826				node->file_entity_tag = NULL;
827			}
828			node->file_locktoken_uid = 0;
829			if ( node->file_locktoken != NULL )
830			{
831				free(node->file_locktoken);
832				node->file_locktoken = NULL;
833			}
834
835			/* free memory used by the node */
836			free(node->name);
837			CFRelease(node->name_ref);
838			if (node->redir_name != NULL)
839				free (node->redir_name);
840
841			(void) internal_remove_attributes(node, TRUE);
842
843			free(node);
844		}
845		node = next_node;
846	}
847}
848
849/*****************************************************************************/
850
851void nodecache_free_nodes(void)
852{
853	lock_node_cache();
854
855	internal_free_nodes();
856
857	unlock_node_cache();
858}
859
860/*****************************************************************************/
861
862static int internal_move_node(
863	struct node_entry *node,		/* the node_entry to move */
864	struct node_entry *new_parent,  /* the new parent node_entry */
865	size_t new_name_length,			/* length of new_name or 0 if not renaming */
866	char *new_name)					/* the utf8 new name of the node or NULL */
867{
868	int error;
869
870	error = 0;
871
872	/* new name? */
873	if ( (new_name_length != 0) && (new_name != NULL) )
874	{
875		CFStringRef name_string;
876		CFComparisonResult compare_result;
877
878		name_string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)new_name, new_name_length, kCFStringEncodingUTF8, false);
879		compare_result = CFStringCompare(name_string, node->name_ref, kCFCompareNonliteral);
880		CFRelease(name_string);
881
882		/* did the name change? */
883		if ( compare_result != kCFCompareEqualTo )
884		{
885			char *name;
886
887			/* allocate space for new name */
888			name = malloc(new_name_length + 1);
889			require_action(name != NULL, malloc_name, error = errno; webdav_kill(-1));
890
891			free(node->name);
892			CFRelease(node->name_ref);
893			node->name = name;
894			memcpy(node->name, new_name, new_name_length);
895			node->name[new_name_length] = '\0';
896			node->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, node->name, kCFStringEncodingUTF8, kCFAllocatorNull);
897			node->name_length = new_name_length;
898		}
899	}
900
901	/* change parents? */
902	if ( node->parent != new_parent )
903	{
904		/* move the node_entry to the new parent */
905		LIST_REMOVE(node, entries);
906		LIST_INSERT_HEAD(&new_parent->children, node, entries);
907		node->parent = new_parent;
908	}
909
910malloc_name:
911
912	return ( error );
913}
914
915/*****************************************************************************/
916
917int nodecache_move_node(
918	struct node_entry *node,		/* the node_entry to move */
919	struct node_entry *new_parent,  /* the new parent node_entry */
920	size_t new_name_length,			/* length of new_name or 0 if not renaming */
921	char *new_name)					/* the utf8 new name of the node or NULL */
922{
923	int error;
924
925	lock_node_cache();
926
927	error = internal_move_node(node, new_parent, new_name_length, new_name);
928
929	unlock_node_cache();
930
931	return ( error );
932}
933
934/*****************************************************************************/
935
936/* delete dir_node's descendents and then delete dir_node */
937static int delete_node_tree(
938	struct node_entry *node,		/* the node_entry to delete */
939	int recursive)					/* delete recursively if node is a directory */
940{
941	int error;
942
943	error = 0;
944
945	if ( recursive )
946	{
947		struct node_entry *child_node;
948
949		child_node = (node->children).lh_first;
950		while ( child_node != NULL )
951		{
952			struct node_entry *next_node;
953
954			next_node = child_node->entries.le_next;
955			error = delete_node_tree(child_node, TRUE);
956			require_noerr(error, delete_child_node);
957
958			child_node = next_node;
959		}
960	}
961	else
962	{
963		require_action((node->children).lh_first == NULL, has_children, error = EBUSY);
964	}
965
966	/* move node over to the deleted list and mark it as deleted  */
967	error = internal_move_node(node, g_deleted_root_node, 0, NULL);
968	require_noerr(error, internal_move_node);
969
970	node->flags |= nodeDeletedMask;
971	node->attr_time = 0;
972	node->attr_stat_info.attr_create_time.tv_sec = 0;
973
974internal_move_node:
975has_children:
976delete_child_node:
977
978	return ( error );
979}
980
981/*****************************************************************************/
982
983int nodecache_delete_node(
984	struct node_entry *node,		/* the node_entry to delete */
985	int recursive)					/* delete recursively if node is a directory */
986{
987	int error;
988
989	lock_node_cache();
990
991	error = delete_node_tree(node, recursive);
992
993	unlock_node_cache();
994
995	return ( error );
996}
997
998/*****************************************************************************/
999
1000/* invalidate dir_node's children's node_times */
1001static int internal_invalidate_directory_node_time(struct node_entry *dir_node)
1002{
1003	int error;
1004	struct node_entry *node;
1005
1006	error = 0;
1007
1008	require_action(dir_node->node_type == WEBDAV_DIR_TYPE, not_directory, error = ENOTDIR);
1009
1010	LIST_FOREACH(node, &(dir_node->children), entries)
1011	{
1012		node->node_time = 0;
1013	}
1014
1015not_directory:
1016
1017	return ( error );
1018}
1019
1020/*****************************************************************************/
1021
1022int nodecache_invalidate_directory_node_time(
1023	struct node_entry *dir_node)		/* parent directory node */
1024{
1025	int error;
1026
1027	lock_node_cache();
1028
1029	error = internal_invalidate_directory_node_time(dir_node);
1030
1031	unlock_node_cache();
1032
1033	return ( error );
1034}
1035
1036/*****************************************************************************/
1037
1038/* invalidate dir_node's children's node_times */
1039static int internal_delete_invalid_directory_nodes(struct node_entry *dir_node)
1040{
1041	int error;
1042	struct node_entry *node;
1043
1044	error = 0;
1045
1046	require_action(dir_node->node_type == WEBDAV_DIR_TYPE, not_directory, error = ENOTDIR);
1047
1048	node = (&(dir_node->children))->lh_first;
1049	while ( node != NULL )
1050	{
1051		struct node_entry *next_node;
1052
1053		next_node = node->entries.le_next;
1054		if ( node->node_time == 0 )
1055		{
1056			//syslog(LOG_ERR,"NODE NAME IS %s",node->name);
1057			error = delete_node_tree(node, TRUE);
1058			require_noerr(error, delete_node_tree);
1059		}
1060		node = next_node;
1061	}
1062
1063delete_node_tree:
1064not_directory:
1065
1066	return ( error );
1067}
1068
1069/*****************************************************************************/
1070
1071int nodecache_delete_invalid_directory_nodes(
1072	struct node_entry *dir_node)		/* parent directory node */
1073{
1074	int error;
1075
1076	lock_node_cache();
1077
1078	error = internal_delete_invalid_directory_nodes(dir_node);
1079
1080	unlock_node_cache();
1081
1082	return ( error );
1083}
1084
1085/*****************************************************************************/
1086
1087/*
1088 * nodecache_get_path_from_node
1089 *
1090 * Builds a UTF-8 slash '/' delimited NULL terminated path from
1091 * the root node (but not including the root node) to the target_node.
1092 *
1093 * Note: If a node in the path has been redirected by an HTTP 3xx redirect, then
1094 * the returned path will be from the redirected node
1095 * to the target_node, and the 'pathHasRedirection' output arg is set to true.
1096 *
1097 * If the node is directory, the path MUST end with a slash.
1098 * The path MUST NOT start with a slash since this is always a relative path.
1099 * If result is 0 (no error), then a newly allocated buffer containing the
1100 * path is returned and the caller is responsible for freeing it.
1101 */
1102static int internal_get_path_from_node(
1103	struct node_entry *target_node,
1104	bool *pathHasRedirection,		/* true if path contains a URL from a redirected node (http 3xx redirect) */
1105	char **path)
1106{
1107	int error;
1108	struct node_entry *cur_node;
1109	char *cur_ptr;
1110	char *pathbuf;
1111	size_t path_len;
1112
1113	error = 0;
1114	pathbuf = NULL;
1115	path_len = 0;
1116	*pathHasRedirection = false;
1117
1118	require_action(!NODE_IS_DELETED(target_node), node_deleted, error = ENOENT);
1119
1120	pathbuf = malloc(PATH_MAX);
1121	require_action(pathbuf != NULL, malloc_pathbuf, error = errno);
1122
1123	if ( target_node == g_root_node )
1124	{
1125		/* nothing to do */
1126		*pathbuf = '\0';
1127	}
1128	else
1129	{
1130		/* point at last character and put in the string terminator */
1131		cur_ptr = pathbuf + PATH_MAX - 1;
1132		*cur_ptr = '\0';
1133
1134		cur_node = target_node;
1135		while ( cur_node != g_root_node )
1136		{
1137			require_action(cur_node != NULL, name_too_long, error = ENAMETOOLONG);
1138
1139			/* was this node redirected? */
1140			if (cur_node->isRedirected == TRUE) {
1141				/* make sure there's enough room for the node redir_name and a slash */
1142				require_action((cur_ptr - cur_node->redir_name_length + 1) >= pathbuf,
1143							   name_too_long, error = ENAMETOOLONG);
1144
1145				/* add a slash */
1146				--cur_ptr;
1147				*cur_ptr = '/';
1148
1149				/* copy in the redir_name */
1150				cur_ptr -= cur_node->redir_name_length;
1151				memcpy(cur_ptr, cur_node->redir_name, cur_node->redir_name_length);
1152
1153				*pathHasRedirection = true;
1154				break;
1155			}
1156
1157			/* make sure there's enough room for the next node name and a slash */
1158			require_action((cur_ptr - cur_node->name_length + 1) >= pathbuf,
1159				name_too_long, error = ENAMETOOLONG);
1160
1161			/* add a slash */
1162			--cur_ptr;
1163			*cur_ptr = '/';
1164
1165			/* copy in the name */
1166			cur_ptr -= cur_node->name_length;
1167			memcpy(cur_ptr, cur_node->name, cur_node->name_length);
1168			cur_node = cur_node->parent;
1169		}
1170
1171		/* move the path to the front of the buffer */
1172		path_len = strlen(cur_ptr);
1173		memmove(pathbuf, cur_ptr, path_len + 1);
1174
1175		/* remove trailing slash if not directory */
1176		if ( target_node->node_type != WEBDAV_DIR_TYPE )
1177		{
1178			--(path_len);
1179			pathbuf[path_len] = '\0';
1180		}
1181	}
1182
1183name_too_long:
1184malloc_pathbuf:
1185node_deleted:
1186
1187	if ( error )
1188	{
1189		if ( pathbuf != NULL )
1190		{
1191			free(pathbuf);
1192			pathbuf = NULL;
1193		}
1194	}
1195
1196	*path = pathbuf;
1197
1198	return ( error );
1199}
1200
1201/*****************************************************************************/
1202
1203int nodecache_get_path_from_node(
1204	struct node_entry *node,		/* -> node */
1205	bool *pathHasRedirection,		/* true if path contains a URL from a redirected node (http 3xx redirect) */
1206	char **path)					/* <- relative path to root node */
1207{
1208	int error;
1209
1210	lock_node_cache();
1211
1212	error = internal_get_path_from_node(node, pathHasRedirection, path);
1213
1214	unlock_node_cache();
1215
1216	return ( error );
1217}
1218
1219/*****************************************************************************/
1220
1221int nodecache_redirect_node(
1222	CFURLRef url,						/* original url that caused the redirect */
1223	struct node_entry *redirected_node,	/* the node_entry that was redirected */
1224	CFHTTPMessageRef responseRef,		/* the 3xx redirect response message */
1225	CFIndex statusCode)					/* 3xx status code from response message */
1226{
1227	CFMutableStringRef newLocationRef;
1228	CFStringRef locationRef, mmeHostRef, tmpStringRef;
1229	CFURLRef newBaseURL;
1230	boolean_t result, isRootNode = false;
1231	size_t name_ptr_len;
1232	char *name_ptr;
1233	int error = 0;
1234
1235	newLocationRef = NULL;
1236	locationRef = NULL;
1237	mmeHostRef = NULL;
1238
1239	if (responseRef == NULL) {
1240		syslog(LOG_ERR, "%s: NULL responseRef\n", __FUNCTION__);
1241		error = EIO;
1242		goto out;
1243	}
1244
1245	newLocationRef = CFStringCreateMutable(kCFAllocatorDefault,0);
1246	if (newLocationRef == NULL){
1247		syslog(LOG_ERR, "%s: no mem for newLocationRef\n", __FUNCTION__);
1248		error = ENOMEM;
1249		goto out;
1250	}
1251
1252	// First need to build a Location string
1253	if (statusCode == 330) {
1254		// This is a MobileMe Realm type of redirection.
1255		// Need to pull out X-Apple-MMe-Host
1256		mmeHostRef = CFHTTPMessageCopyHeaderFieldValue(responseRef, CFSTR("X-Apple-MMe-Host"));
1257
1258		if (mmeHostRef == NULL) {
1259			syslog(LOG_ERR, "%s: 330 Redirection missing MMe-Host header\n", __FUNCTION__);
1260			error = EIO;
1261			goto out;
1262		}
1263
1264		if (gSecureConnection == TRUE)
1265			CFStringAppend(newLocationRef, CFSTR("https://"));
1266		else
1267			CFStringAppend(newLocationRef, CFSTR("http://"));
1268
1269		// add host from X-Apple-MMe-Host header
1270		CFStringAppend(newLocationRef, mmeHostRef);
1271
1272		// fetch the path from the original URL
1273		tmpStringRef = CFURLCopyPath(url);
1274
1275		if (tmpStringRef != NULL) {
1276			CFStringAppend(newLocationRef, tmpStringRef);
1277			CFRelease(tmpStringRef);
1278		}
1279	}
1280	else {
1281		// Not an MMe 330, just grab Location field
1282		locationRef = CFHTTPMessageCopyHeaderFieldValue(responseRef, CFSTR("Location"));
1283
1284		if (locationRef == NULL) {
1285			syslog(LOG_ERR, "%s: 3xx response missing Location field\n", __FUNCTION__);
1286			error = EIO;
1287			return (error);
1288		}
1289
1290		CFStringAppend(newLocationRef, locationRef);
1291	}
1292
1293	if (redirected_node == NULL || redirected_node == g_root_node)
1294		isRootNode = true;
1295	else
1296		isRootNode = false;
1297
1298	// Add or remove trailing slash as needed
1299	if (isRootNode == true) {
1300		// we want a trailing slash
1301		if (CFStringHasSuffix(newLocationRef, CFSTR("/")) == false)
1302			CFStringAppend(newLocationRef, CFSTR("/"));
1303	}
1304	else {
1305		// Not the root node, don't want a trailing slash
1306		if (CFStringHasSuffix(newLocationRef, CFSTR("/")) == true)
1307			CFStringDelete(newLocationRef, CFRangeMake(CFStringGetLength(newLocationRef) - 1, 1));
1308	}
1309
1310	// Setup C string for new redir_name
1311	name_ptr = malloc(WEBDAV_MAX_URI_LEN + 1);
1312	if (name_ptr == NULL) {
1313		syslog(LOG_ERR, "%s: no mem for name_ptr\n", __FUNCTION__);
1314		error = ENOMEM;
1315		goto out;
1316	}
1317
1318	result = CFStringGetCString(newLocationRef, name_ptr, WEBDAV_MAX_URI_LEN, kCFStringEncodingUTF8);
1319	if (result == false) {
1320		syslog(LOG_ERR, "%s: CFStringGetCString returned false for newLocationRef\n", __FUNCTION__);
1321		error = EIO;
1322		free(name_ptr);
1323		goto out;
1324	}
1325
1326	name_ptr_len = strlen(name_ptr);
1327	if (name_ptr_len <= 0) {
1328		error = EIO;
1329		free(name_ptr);
1330		goto out;
1331	}
1332
1333	// Block any attempt to redirect from a secure host to a non-secure host
1334	if (gSecureConnection == TRUE) {
1335		if (CFStringHasPrefix(newLocationRef, CFSTR("https://")) == false) {
1336			syslog(LOG_ERR, "%s: redirect to non-secure host denied: %s\n", __FUNCTION__, name_ptr);
1337			error = EIO;
1338			free(name_ptr);
1339			goto out;
1340		}
1341	}
1342
1343	lock_node_cache();
1344
1345	if (isRootNode == false) {
1346		if (redirected_node->isRedirected == true) {
1347			// cleanup old redirection
1348			if (redirected_node->redir_name != NULL)
1349				free(redirected_node->redir_name);
1350		}
1351
1352		// now change node state
1353		redirected_node->isRedirected = true;
1354		redirected_node->redir_name = name_ptr;
1355		redirected_node->redir_name_length = name_ptr_len;
1356
1357		syslog(LOG_DEBUG, "Node %s redirected to: %s", redirected_node->name, name_ptr);
1358	}
1359	else {
1360		// The root node, g_root_node is being redirected
1361		// Create the new gBaseURL
1362		newBaseURL = CFURLCreateAbsoluteURLWithBytes(kCFAllocatorDefault, (UInt8 *)name_ptr, name_ptr_len, kCFStringEncodingUTF8, NULL, FALSE);
1363		if (newBaseURL == NULL) {
1364			syslog(LOG_ERR, "%s: Location field was not legal UTF8: %s\n", __FUNCTION__, name_ptr);
1365			free(name_ptr);
1366			error = ENOMEM;
1367			goto create_baseurl;
1368		}
1369
1370		if (g_root_node->isRedirected == true) {
1371			// cleanup old redirection
1372			if (g_root_node->redir_name != NULL)
1373				free(g_root_node->redir_name);
1374		}
1375
1376		// Now change node state
1377		CFRelease(gBaseURL);
1378		gBaseURL = newBaseURL;
1379
1380		if (gBasePath != NULL) {
1381			CFRelease(gBasePath);
1382			gBasePath = NULL;
1383		}
1384
1385		gBasePath = CFURLCopyPath(gBaseURL);
1386		if (gBasePath != NULL) {
1387			CFStringGetCString(gBasePath, gBasePathStr, MAXPATHLEN, kCFStringEncodingUTF8);
1388		}
1389
1390		g_root_node->isRedirected = true;
1391		g_root_node->redir_name = name_ptr;
1392		g_root_node->redir_name_length = name_ptr_len;
1393
1394		syslog(LOG_DEBUG, "Base node redirected to: %s", name_ptr);
1395	}
1396
1397create_baseurl:
1398	unlock_node_cache();
1399out:
1400	if (newLocationRef != NULL)
1401		CFRelease(newLocationRef);
1402	if (locationRef != NULL)
1403		CFRelease(locationRef);
1404	if (mmeHostRef != NULL)
1405		CFRelease(mmeHostRef);
1406
1407	return (error);
1408}
1409
1410
1411/* Returns a CFArray containing (1) the a_node's file_locktoken (if exists) */
1412/* AND (2) all the a_node's children's file_locktokens (if any exists) */
1413static CFArrayRef internal_get_locktokens(struct node_entry *a_node)
1414{
1415	int error;
1416	CFMutableArrayRef arr;
1417	CFStringRef lockTokenStr;
1418	struct node_entry *node;
1419
1420	arr = NULL;
1421	error = 0;
1422	arr = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
1423	require(arr != NULL, create_array);
1424
1425	// First do parent
1426	if (a_node->file_locktoken != NULL) {
1427		lockTokenStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("(<%s>)"), a_node->file_locktoken);
1428		if (lockTokenStr != NULL) {
1429			CFArrayAppendValue(arr, lockTokenStr);
1430			CFRelease(lockTokenStr);	// we don't need to hold a reference
1431		}
1432		else {
1433			// apparently no mem to do our job, so punt.
1434			goto done;
1435		}
1436	}
1437
1438	// We're done if no child nodes
1439	if (a_node->node_type != WEBDAV_DIR_TYPE)
1440		goto done;
1441
1442	// Now get child locktokens
1443	LIST_FOREACH(node, &(a_node->children), entries)
1444	{
1445		if (node->file_locktoken != NULL) {
1446			lockTokenStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("(<%s>)"), node->file_locktoken);
1447			if (lockTokenStr != NULL) {
1448				CFArrayAppendValue(arr, lockTokenStr);
1449				CFRelease(lockTokenStr);	// we don't need to hold a reference
1450			}
1451		}
1452	}
1453
1454done:
1455	if (!CFArrayGetCount(arr)) {
1456		// no file_locktokens found, return NULL
1457		CFRelease(arr);
1458		arr = NULL;
1459	}
1460
1461create_array:
1462
1463	return ( arr );
1464}
1465
1466/*****************************************************************************/
1467
1468CFArrayRef nodecache_get_locktokens(struct node_entry *a_node)
1469{
1470	CFArrayRef arr;
1471
1472	lock_node_cache();
1473
1474	arr = internal_get_locktokens(a_node);
1475
1476	unlock_node_cache();
1477
1478	return ( arr );
1479}
1480
1481/*****************************************************************************/
1482static int init_node_cache_lock(void)
1483{
1484	int error;
1485	pthread_mutexattr_t mutexattr;
1486
1487	error = pthread_mutexattr_init(&mutexattr);
1488	require_noerr(error, pthread_mutexattr_init);
1489
1490	error = pthread_mutex_init(&g_node_cache_lock, &mutexattr);
1491	require_noerr(error, pthread_mutex_init);
1492
1493pthread_mutex_init:
1494pthread_mutexattr_init:
1495
1496	return ( error );
1497}
1498
1499/*****************************************************************************/
1500
1501/*
1502 * CFURLRef nodecache_get_baseURL(void)
1503 *
1504 * Returns a retained reference to the current gBaseURL. Caller must CFRelease
1505 * the returned reference when done with it.
1506 *
1507 */
1508CFURLRef nodecache_get_baseURL(void)
1509{
1510	CFURLRef baseURL;
1511
1512	lock_node_cache();
1513	CFRetain(gBaseURL);
1514	baseURL = gBaseURL;
1515	unlock_node_cache();
1516	return baseURL;
1517}
1518
1519/*****************************************************************************/
1520 void lock_node_cache(void)
1521{
1522	int error;
1523
1524	error = pthread_mutex_lock(&g_node_cache_lock);
1525	require_noerr_action(error, pthread_mutex_lock, webdav_kill(-1));
1526
1527pthread_mutex_lock:
1528
1529	return;
1530}
1531
1532/*****************************************************************************/
1533
1534void unlock_node_cache(void)
1535{
1536	int error;
1537
1538	error = pthread_mutex_unlock(&g_node_cache_lock);
1539	require_noerr_action(error, pthread_mutex_unlock, webdav_kill(-1));
1540
1541pthread_mutex_unlock:
1542
1543	return;
1544}
1545
1546/*****************************************************************************/
1547