#import "NSLVnode.h"
#import "Controller.h"
#import "AMString.h"
#import "AMMap.h"
#import "automount.h"
#import "log.h"
#import <syslog.h>
#import <string.h>
#import <stdlib.h>
#import <stdio.h>
#import <netdb.h>
#import "nfs_prot.h"
#import "Server.h"
#import <sys/types.h>
#import <sys/stat.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <netdb.h>
#import <unistd.h>
#import <stdlib.h>
#import <string.h>
#import <sys/socket.h>
#import <net/if.h>
#import <sys/ioctl.h>
#import <rpc/types.h>
#import <rpc/xdr.h>
#import <rpc/auth.h>
#import <rpc/clnt.h>
#import <rpc/svc.h>
#import <errno.h>
#import "nfs_prot.h"
#import "automount.h"
#import "log.h"
#import "mount.h"
#import "AMString.h"
#import "NSLUtil.h"
#import <CoreServices/CoreServices.h>
#import <CoreServices/CoreServicesPriv.h>
/* Re-enumerate folders at least once an hour: */
/* Refresh all directories at least once every 10 hours: */
//#define NSL_REFRESH_RATE (10*60*60)
#define NSL_REFRESH_RATE 0
#define NSL_ERROR_PERSISTENCE_DURATION 10
#define DONTINVALIDATEINTERMEDIATECONTAINERS 1
#define LIMITCHANGENOTIFICATIONS 1
#define COUNTGENERATIONS 1
extern BOOL doServerMounts;
static String *generateLinkTarget(Vnode *v);
static void setupLink(Vnode *v);
static BOOL anyChildMounted(const char *path);
static char gLocalHostName[] = "localhost";
static char gInvalidateCue[] = ".._invalidatecache";
extern BOOL gUserLoggedIn;
void NSLVnodeNewSearchResultAlert(SearchContextPtr callContext)
{
char request_code[1];
request_code[0] = REQ_PROCESS_RESULTS;
if (gWakeupFDs[1] != -1) {
(void)write(gWakeupFDs[1], request_code, sizeof(request_code));
};
}
@implementation NSLVnode
- (NSLVnode *)init
{
[super init];
generation = 0;
apparentName = NULL;
NSLObjectType = kNetworkObjectTypeNone;
fixedEntry = NO;
havePopulated = NO;
INIT_SEARCHRESULTLIST(&neighborhoodSearchResults, NSLObject.neighborhood);
neighborhoodSearchStarted = NO;
neighborhoodSearchComplete = NO;
INIT_SEARCHRESULTLIST(&servicesSearchResults, NSLObject.neighborhood);
servicesSearchStarted = NO;
servicesSearchComplete = NO;
lastNotification.tv_sec = 0;
lastNotification.tv_usec = 0;
lastSeen.tv_sec = 0;
lastSeen.tv_usec = 0;
mountInProgress = NO;
currentContentGeneration = 0;
return self;
}
- (NSLVnode *)newNeighborhoodWithName:(String *)neighborhoodNameString neighborhood:(NSLNeighborhood)neighborhood
{
NSLVnode *v;
v = [NSLVnode alloc];
if (v == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.newNeighborhoodWithName: Failed to allocate NSLVnode; aborting.");
goto Error_Exit;
};
[v init];
[v setApparentName:neighborhoodNameString];
[v setNSLObject:neighborhood type:kNetworkNeighborhood];
[v setMap:map];
[controller registerVnode:v];
[self addChild:v];
return v;
Error_Exit:
if (v) [v release];
return nil;
}
- (NSLVnode *)newServiceWithName:(String *)newServiceName service:(NSLService)service serviceURL:(char *)serverURL
{
NSLVnode *v;
Server *newserver = nil;
String *serversrc = nil;
String *serviceURL = nil;
v = [NSLVnode alloc];
if (v == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.newServerWithName: Failed to allocate NSLVnode; aborting.");
goto Error_Exit;
};
[v init];
if (serverURL) {
serviceURL = [String uniqueString:serverURL];
if (serviceURL == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.initAsServerWithName: Failed to allocate serviceURL; aborting.");
goto Error_Exit;
};
};
newserver = [controller serverWithName:newServiceName];
if (newserver == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.initAsServerWithName: Failed to allocate controller; aborting.");
goto Error_Exit;
};
if ([newserver isLocalHost])
{
serversrc = [String uniqueString:"/"];
[v setMounted:YES];
[v setFakeMount:YES];
} else {
serversrc = [String uniqueString:"*"];
};
if (serversrc == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.initAsServerWithName: Failed to allocate serversrc; aborting.");
goto Error_Exit;
};
[v setApparentName:newServiceName];
[v setServer:newserver];
[v setSource:serversrc];
[v setUrlString:serviceURL];
[v setType:NFLNK];
[v setMode:(S_ISVTX | 0755 | NFSMODE_LNK)];
[v setVfsType:[String uniqueString:"url"]];
[v setNSLObject:service type:kNetworkServer];
/* [v setupOptions:opts]; */
[v setMap:map];
[controller registerVnode:v];
// sys_msg(debug, LOG_DEBUG, "NSLVnode.newServiceWithName: Adding new offspring [self addChild:v];
/* This relies on the superlink/child/map information to construct its path and must be done last... */
setupLink(v);
return v;
Error_Exit:
if (serversrc) [serversrc release];
if (newserver) [newserver release];
if (serviceURL) [serviceURL release];
if (v) [v release];
return nil;
}
- (NSLVnode *)newSymlinkWithName:(String *)newSymlink target:(char *)target
{
String *localLinkSpec;
String *targetString;
NSLVnode *v;
localLinkSpec = [String uniqueString:gLocalHostName];
if (localLinkSpec == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.newSymlinkWithName: Failed to allocate localLinkSpec; aborting.");
goto Error_Exit;
};
targetString = [String uniqueString:target];
if (targetString == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.newSymlinkWithName: Failed to allocate targetString; aborting.");
goto Error_Exit;
};
v = [self newServiceWithName:localLinkSpec service:nil serviceURL:nil];
if (v == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.newSymlinkWithName: Failed to allocate NSLVnode; aborting.");
goto Error_Exit;
};
[v setMode:([v mode] & ~S_ISVTX)]; /* Turn off the sticky bit: this is not a special mount-trigger link */
[v setApparentName:newSymlink];
[v setLink:targetString];
[v setFixedEntry:YES];
return v;
Error_Exit:
return nil;
}
- (void)dealloc
{
[self stopSearchesInProgress];
[self freeNSLObject];
if (apparentName) [apparentName release];
[super dealloc];
}
- (void)RequestFinderNotificationFor:(char *)directoryPath using:(CFMessagePortRef)requestMessagePort
{
CFDataRef notificationRequestData;
SInt32 result;
if (requestMessagePort == NULL) {
sys_msg(debug, LOG_DEBUG, "Ignoring request for Finder notification for changes to '%s'...", directoryPath);
return;
};
if (lastNotification.tv_sec == attributes.mtime.seconds &&
lastNotification.tv_usec == attributes.mtime.useconds)
{
sys_msg(debug, LOG_DEBUG, "Ignoring duplicate request for Finder notification for '%s'...", directoryPath);
return;
}
sys_msg(debug, LOG_DEBUG, "Requesting Finder notification for changes to '%s'...", directoryPath);
notificationRequestData = CFDataCreate(kCFAllocatorDefault, directoryPath, strlen(directoryPath) + 1);
if (notificationRequestData) {
result = CFMessagePortSendRequest( requestMessagePort, /* target message port */
0, /* message ID */
notificationRequestData, /* data to be sent */
0, /* send timeout (Sec.) */
0, /* receive timeout (Sec.) */
NULL, /* reply mode */
NULL); /* return data */
if (result == kCFMessagePortSuccess) {
lastNotification.tv_sec = attributes.mtime.seconds;
lastNotification.tv_usec = attributes.mtime.useconds;
} else {
sys_msg(debug, LOG_ERR, "Failed request for Finder notification; result = };
CFRelease(notificationRequestData);
}
}
- (void)considerFinderNotification
{
if (LIMITCHANGENOTIFICATIONS && (lastNotification.tv_sec == attributes.mtime.seconds)) {
sys_msg(debug, LOG_DEBUG, "considerFinderNotification: Deferring repeat notification for '%s'...", [[self path] value]);
/* Set a time for the number of microseconds until mtime would tick over to the next whole second
and leave well enough alone for now: */
ualarm(1000000 - attributes.mtime.useconds, 0);
} else {
[self RequestFinderNotificationFor:[[self path] value] using:[(NSLMap *)[self map] notificationMessagePort]];
}
}
- (void)triggerDeferredNotifications:(SearchContext *)searchContext
{
if (attributes.mtime.seconds < lastNotification.tv_sec)
{
sys_msg(debug, LOG_DEBUG, "triggerDeferredNotifications: mtime < lastNotification for '%s'?", [[self path] value]);
return;
}
/*
* If the node changed since the last notification was sent, so send another one now.
*
* Be careful how the times are compared since they are essentially a double-precision number. The comparison above
* means that mtime >= lastNotification. Without that test, the second comparison below would also need to check
* attributes.mtime.seconds == lastNotification.tv_sec.
*/
if ((attributes.mtime.seconds > lastNotification.tv_sec) || (attributes.mtime.useconds > lastNotification.tv_usec)) {
[self RequestFinderNotificationFor:[[self path] value] using:[(NSLMap *)[self map] notificationMessagePort]];
}
}
- (void)markDirectoryChanged
{
[super markDirectoryChanged];
[self considerFinderNotification];
}
- (void)invalidateRecursively:(BOOL)invalidateDescendants notifyFinder:(BOOL)notifyFinder;
{
struct timeval now;
int offspring_count, neighborhood_offspring;
if (!havePopulated) return;
sys_msg(debug, LOG_DEBUG, "NSLVnode.invalidateRecursively: invalidating '%s'...", [[self path] value]);
offspring_count = [[self children] count];
neighborhood_offspring = 0;
if (invalidateDescendants) {
int i;
NSLVnode *offspring;
for (i = 0; i < offspring_count; ++i) {
offspring = [[self children] objectAtIndex:i];
if ([offspring getobjectType] == kNetworkNeighborhood) {
++neighborhood_offspring;
[offspring invalidateRecursively:YES notifyFinder:notifyFinder];
};
};
};
#if DONTINVALIDATEINTERMEDIATECONTAINERS
if ((offspring_count > 0) && (neighborhood_offspring == offspring_count)) {
/* This object contains only other directories: no need to invalidate it... */
return;
};
#endif
/* This will trigger a refresh on the next reference: */
lastUpdate.tv_sec = 0;
lastUpdate.tv_usec = 0;
do {
gettimeofday(&now, NULL);
} while ((now.tv_sec == attributes.atime.seconds) && (now.tv_usec == attributes.atime.useconds));
attributes.atime.seconds = now.tv_sec;
attributes.atime.useconds = now.tv_usec;
attributes.mtime = attributes.atime;
attributes.ctime = attributes.atime;
if (notifyFinder) {
[self RequestFinderNotificationFor:[[self path] value] using:[(NSLMap *)[self map] notificationMessagePort]];
};
sys_msg(debug, LOG_DEBUG, "NSLVnode.invalidateRecursively: invalidation of '%s' complete.", [[self path] value]);
}
- (unsigned long)getGeneration
{
return generation;
}
- (void)setGeneration:(unsigned long)newGeneration
{
generation = newGeneration;
}
- (String *)apparentName
{
return apparentName;
}
- (String *)makeExternalName:(String *)n
{
char *externalName;
char *cp, *np;
String *newExternalName;
externalName = (char *)malloc(strlen([n value]) + 1);
if (externalName == NULL) {
sys_msg(debug, LOG_ERR, "Failed to allocate memory for external representation of '%s'", [n value]);
return [n retain];
};
for (cp = [n value], np = externalName; *cp; cp++) {
*np++ = (*cp == '/') ? ':' : *cp;
};
*np = (char)0;
sys_msg(debug, LOG_DEBUG, "\tnew external name is '%s'", externalName);
newExternalName = [String uniqueString:externalName];
free(externalName);
if (newExternalName == NULL) {
sys_msg(debug, LOG_ERR, "Failed to allocate memory for external String* representation of '%s'", [n value]);
newExternalName = [n retain];
};
return newExternalName;
}
- (void)setApparentName:(String *)n
{
apparentName = [n retain];
if (!strchr([n value], '/')) {
[self setName:n];
} else {
String *newExternalName = [self makeExternalName:n];
[self setName:newExternalName];
[newExternalName release];
};
}
- (NetworkObjectType)getobjectType {
return NSLObjectType;
}
- (void)freeNSLObject {
switch (NSLObjectType) {
case kNetworkNeighborhood:
if (NSLObject.neighborhood) NSLFreeNeighborhood(NSLObject.neighborhood);
NSLObject.neighborhood = NULL;
break;
case kNetworkServer:
if (NSLObject.service) CFRelease(NSLObject.service);
NSLObject.service = NULL;
break;
default:
break;
};
NSLObjectType = kNetworkObjectTypeNone;
}
- (void)setNSLObject:(const void *)object type:(NetworkObjectType)objecttype {
[self freeNSLObject];
NSLObjectType = objecttype;
/*
* The following code does not copy the object being passed in; instead it is
* assumed here that the object being passed in is either persistent without
* any refcount from this pointer or else is a copy that will only be freed above
* on the next change of NSLObject:
*/
switch (objecttype) {
case kNetworkNeighborhood:
NSLObject.neighborhood = (NSLNeighborhood)object;
break;
case kNetworkServer:
NSLObject.service = (NSLService)object;
break;
default:
break;
};
}
- (BOOL)fixedEntry
{
return fixedEntry;
}
- (void)setFixedEntry:(BOOL)newFixedEntryStatus
{
fixedEntry = newFixedEntryStatus;
}
- (void)depopulateDescendants:(BOOL)depopulateDescendants destroyEmptyNeighborhoods:(BOOL)destroyEmptyNeighborhoods
{
int target_offspring = 0;
NSLVnode *targetVnode = nil;
BOOL contentsChanged = NO;
struct timeval now;
sys_msg(debug, LOG_DEBUG, "NSLVnode.depopulate: depopulating neighborhood '%s'...", [[self apparentName] value]);
if ([self children]) {
while ((NSLVnode *)([[self children] objectAtIndex:target_offspring]) != targetVnode) {
targetVnode = (NSLVnode *)[[self children] objectAtIndex:target_offspring];
if (targetVnode == nil) {
++target_offspring; /* Skip this entry */
} else {
if ([targetVnode getobjectType] == kNetworkNeighborhood) {
if (depopulateDescendants) {
[targetVnode depopulateDescendants:YES destroyEmptyNeighborhoods:destroyEmptyNeighborhoods];
};
if (destroyEmptyNeighborhoods && ([[targetVnode children] count] == 0)) {
sys_msg(debug, LOG_DEBUG, "NSLVnode.depopulate: discarding neighborhood '%s'...", [[targetVnode apparentName] value]);
[controller destroyVnode:targetVnode];
contentsChanged = YES;
} else {
++target_offspring; /* Skip this entry */
};
} else if ([targetVnode getobjectType] == kNetworkServer) {
if ([targetVnode mounted]) {
++target_offspring; /* Skip this entry */
} else {
sys_msg(debug, LOG_DEBUG, "NSLVnode.depopulate: discarding server '%s'...", [[targetVnode apparentName] value]);
[controller destroyVnode:targetVnode];
contentsChanged = YES;
};
} else {
sys_msg(debug, LOG_DEBUG, "NSLVnode.depopulate: unknown node type ('%s')...", [[targetVnode apparentName] value]);
++target_offspring; /* Skip this entry */
};
};
};
};
havePopulated = NO; /* Reset to trigger a fresh look at things */
if (contentsChanged) {
do {
gettimeofday(&now, NULL);
} while ((now.tv_sec == attributes.atime.seconds) && (now.tv_usec == attributes.atime.useconds));
/* Update this directory's timestamps as well: */
attributes.atime.seconds = now.tv_sec;
attributes.atime.useconds = now.tv_usec;
attributes.mtime = attributes.atime;
attributes.ctime = attributes.atime;
};
}
- (void)destroyVnodeGenerationsPriorTo:(unsigned long)currentGeneration
{
int offspringCount;
int target_offspring;
NSLVnode *targetVnode = nil;
BOOL contentsChanged = NO;
struct timeval now;
sys_msg(debug, LOG_DEBUG, "NSLVnode.destroyVnodeGenerationsPriorTo: destroying generations before #
if ([self children]) {
offspringCount = [[self children] count];
target_offspring = 0;
while (target_offspring < offspringCount) {
targetVnode = (NSLVnode *)[[self children] objectAtIndex:target_offspring];
if ((targetVnode == nil) ||
([targetVnode fixedEntry]) ||
([targetVnode getGeneration] >= currentGeneration)) {
++target_offspring;
continue;
};
/* This Vnode belongs to an older generation: */
if ([targetVnode getobjectType] == kNetworkNeighborhood) {
sys_msg(debug, LOG_DEBUG, "NSLVnode.destroyVnodeGenerationsPriorTo: destroying offspring of '%s'...", [[targetVnode apparentName] value]);
[targetVnode depopulateDescendants:YES destroyEmptyNeighborhoods:YES];
};
sys_msg(debug, LOG_DEBUG, "NSLVnode.destroyVnodeGenerationsPriorTo: destroying '%s'...", [[targetVnode apparentName] value]);
[controller destroyVnode:targetVnode];
--offspringCount;
contentsChanged = YES;
};
};
if (contentsChanged) {
do {
gettimeofday(&now, NULL);
} while ((now.tv_sec == attributes.atime.seconds) && (now.tv_usec == attributes.atime.useconds));
/* Update this directory's timestamps as well: */
attributes.atime.seconds = lastUpdate.tv_sec;
attributes.atime.useconds = lastUpdate.tv_usec;
attributes.mtime = attributes.atime;
attributes.ctime = attributes.atime;
};
}
- (Vnode *)lookupByApparentNameWithoutPopulating:(String *)n
{
if (!strchr([n value], '/')) {
return [self lookupWithoutPopulating:n];
} else {
String *e = [self makeExternalName:n];
Vnode *v = [self lookupWithoutPopulating:e];
[e release];
return v;
};
}
- (Vnode *)lookupWithoutPopulating:(String *)n
{
return [super lookup:n];
}
- (void)stopSearchesInProgress
{
NSLError error;
if (neighborhoodSearchStarted) {
if (gTerminating) {
sys_msg(debug, LOG_DEBUG, "stopSearchesInProgress: taking no action on neighborhood search during shutdown.");
} else {
sys_msg(debug, LOG_DEBUG, "stopSearchesInProgress: stopping neighborhood search in error = NSLDeleteRequest(neighborhoodSearchContext.searchRef);
if (error.theErr) {
sys_msg(debug, LOG_ERR, "stopSearchesInProgress: error error.theErr, [[self path] value]);
};
};
[(NSLMap *)[self map] deleteSearchInProgress:&neighborhoodSearchContext];
neighborhoodSearchStarted = NO;
};
if (servicesSearchStarted) {
if (gTerminating) {
sys_msg(debug, LOG_DEBUG, "stopSearchesInProgress: taking no action on services search during shutdown.");
} else {
sys_msg(debug, LOG_DEBUG, "stopSearchesInProgress: stopping services search in error = NSLDeleteRequest(servicesSearchContext.searchRef);
if (error.theErr) {
sys_msg(debug, LOG_ERR, "stopSearchesInProgress: error error.theErr, [[self path] value]);
};
};
[(NSLMap *)[self map] deleteSearchInProgress:&servicesSearchContext];
servicesSearchStarted = NO;
};
}
- (int)populateWithNeighborhoodsWithGeneration:(unsigned long)newGeneration completely:(BOOL)waitForSearch
{
int result;
if (!neighborhoodSearchStarted) {
INIT_SEARCHCONTEXT(&neighborhoodSearchContext,
self,
[(NSLMap *)map getNSLClientRef],
&neighborhoodSearchResults,
newGeneration,
NSLVnodeNewSearchResultAlert,
self);
result = StartSearchForNeighborhoodsInNeighborhood(NSLObject.neighborhood, &neighborhoodSearchContext);
if (result) {
sys_msg(debug, LOG_ERR, "NSLVnode.populateWithNeighborhoodsWithGeneration: error return result;
};
[(NSLMap *)[self map] recordSearchInProgress:&neighborhoodSearchContext];
neighborhoodSearchStarted = YES;
gettimeofday(&lastUpdate, NULL);
};
if (waitForSearch) {
sys_msg(debug, LOG_DEBUG, "Waiting for neighborhood search completion in [[self path] value], &neighborhoodSearchContext);
WaitForInitialSearchCompletion(&neighborhoodSearchContext);
sys_msg(debug, LOG_DEBUG, "Neighborhood search complete in [[self path] value], &neighborhoodSearchContext);
neighborhoodSearchComplete = YES;
} else {
WaitForCachedSearchCompletion(&neighborhoodSearchContext);
};
return 0;
}
- (unsigned long)populateWithServicesWithGeneration:(unsigned long)newGeneration completely:(BOOL)waitForSearch
{
if (!servicesSearchStarted) {
CFMutableArrayRef serviceListRef;
int result;
serviceListRef = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
if (serviceListRef == NULL) {
sys_msg(debug, LOG_ERR, "NSLVnode.populateWithServicesWithGeneration: couldn't allocate serviceListRef?!");
goto Error_Exit;
};
CFArrayAppendValue( serviceListRef, CFSTR("afp") );
CFArrayAppendValue( serviceListRef, CFSTR("smb") );
CFArrayAppendValue( serviceListRef, CFSTR("cifs") );
CFArrayAppendValue( serviceListRef, CFSTR("nfs") );
CFArrayAppendValue( serviceListRef, CFSTR("webdav") );
CFArrayAppendValue( serviceListRef, CFSTR("ftp") );
INIT_SEARCHCONTEXT(&servicesSearchContext,
self,
[(NSLMap *)map getNSLClientRef],
&servicesSearchResults,
newGeneration,
NSLVnodeNewSearchResultAlert,
self);
result = StartSearchForServicesInNeighborhood( NSLObject.neighborhood, serviceListRef, &servicesSearchContext );
CFRelease(serviceListRef);
if (result) {
sys_msg(debug, LOG_ERR, "NSLVnode.populateWithServicesWithGeneration: error return result;
};
[(NSLMap *)[self map] recordSearchInProgress:&servicesSearchContext];
servicesSearchStarted = YES;
gettimeofday(&lastUpdate, NULL);
};
if (waitForSearch) {
sys_msg(debug, LOG_DEBUG, "Waiting for services search completion in [[self path] value], &servicesSearchContext);
WaitForInitialSearchCompletion(&servicesSearchContext);
sys_msg(debug, LOG_DEBUG, "Services search complete in [[self path] value], &servicesSearchContext);
servicesSearchComplete = YES;
} else {
WaitForCachedSearchCompletion(&servicesSearchContext);
};
Error_Exit:
return 0;
}
/*
* Get sub-neighborhoods and servers for this neighborhood.
*/
- (void)populateCompletely:(BOOL)waitForSearchCompletion;
{
struct timeval now;
BOOL newGeneration = NO;
if (havePopulated) {
if (NSL_REFRESH_RATE == 0) goto Std_Exit; /* No refresh requested */
gettimeofday(&now, NULL);
if (now.tv_sec < (lastUpdate.tv_sec + NSL_REFRESH_RATE)) goto Std_Exit;
[(NSLVnode *)self stopSearchesInProgress];
#if COUNTGENERATIONS
++currentContentGeneration; /* About to populate with a new generation of descendants */
newGeneration = YES;
#endif
};
sys_msg(debug, LOG_DEBUG, "NSLVnode.populateCompletely( waitForSearchCompletion ? "YES" : "NO",
[[self apparentName] value],
currentContentGeneration);
(void)[self populateWithNeighborhoodsWithGeneration:currentContentGeneration completely:waitForSearchCompletion];
(void)[self populateWithServicesWithGeneration:currentContentGeneration completely:waitForSearchCompletion];
#if COUNTGENERATIONS
if (newGeneration) {
[self destroyVnodeGenerationsPriorTo:currentContentGeneration];
#if 0
/* Update this directory's timestamps as well: */
[self markDirectoryChanged];
#endif
};
#endif
if (neighborhoodSearchComplete && servicesSearchComplete) havePopulated = YES;
Std_Exit: ;
return;
}
- (void)processAddResult:(struct SearchResult *)resultEntry
ofType:(NSLResultType)searchType
withGeneration:(unsigned long)searchGeneration
{
String *targetNameString = nil;
NSLVnode *v = nil;
if (searchType == kNetworkNeighborhood) {
/*
* Add a new neighborhood entry:
*/
char *neighborhoodname = NULL;
long neighborhoodnamelength;
if (resultEntry->result.neighborhood == NULL) {
sys_msg(debug, LOG_ERR, "NSLVnode.processAddResult: resultEntry->result.neighborhood is NULL; aborting.");
goto Ignore_Entry;
};
NSLGetNameFromNeighborhood( resultEntry->result.neighborhood, &neighborhoodname, &neighborhoodnamelength );
if ((neighborhoodname == NULL) || (neighborhoodnamelength == 0)) {
sys_msg(debug, LOG_ERR, "NSLVnode.processAddResult: Failed to get neighborhood name for entry; aborting.");
goto Abort_Neighborhood_Entry;
};
targetNameString = [String uniqueString:neighborhoodname];
if (targetNameString == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.processAddResult: Failed to allocate String 'targetNameString'; aborting.");
goto Abort_Neighborhood_Entry;
}
#if 0 /* DEBUG ONLY */
if (1) {
CFStringRef attributeStringRef = NULL;
char attributeCString[64];
attributeStringRef = NSLXNeighborhoodGetAttributeValue( resultEntry->result.neighborhood, kNSLProtocolTypeKey );
if (attributeStringRef == NULL) {
sys_msg(debug, LOG_DEBUG, "NSLVnode.processAddResult: kNSLProtocolTypeKey attributeStringRef is NULL.");
goto skip;
};
(void)CFStringGetCString(attributeStringRef, attributeCString, (CFIndex)sizeof(attributeCString), kCFStringEncodingUTF8);
sys_msg(debug, LOG_DEBUG, "NSLVnode.processAddResult: kNSLProtocolTypeKey for '%s' = '%s'...",
neighborhoodname,
attributeCString);
skip: ;
};
#endif
v = (NSLVnode *)[self lookupByApparentNameWithoutPopulating:targetNameString];
if (v && ([v getobjectType] != kNetworkNeighborhood)) {
[controller destroyVnode:v];
v = nil;
};
if (v == nil) {
sys_msg(debug, LOG_DEBUG, "NSLVnode.processAddResult: Adding neighborhood /* Note: newNeighborhoodWithName does not copy the NSL object AGAIN; it just takes over 'ownership' from the caller */
v = [self newNeighborhoodWithName:targetNameString neighborhood:resultEntry->result.neighborhood];
if (v == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.processAddResult: Failed to allocate new neighborhood node; aborting.");
goto Abort_Neighborhood_Entry;
};
resultEntry->result.neighborhood = nil; /* NSL object is now 'owned' by newly created Vnode... */
};
[v setGeneration:searchGeneration];
/* [v setVfsType:??? ]; */
goto Next_Neighborhood_Entry;
Abort_Neighborhood_Entry:
/* Release any data structures otherwise permanently incorporated into this new node: */
Next_Neighborhood_Entry:
/* Release any data structures used to construct this new node: */
if (resultEntry->result.neighborhood) NSLFreeNeighborhood(resultEntry->result.neighborhood);
} else if (searchType == kNetworkServer) {
/*
* Add a new server entry:
*/
CFStringRef urlStringRef = NULL;
CFStringRef serviceNameRef;
char servicename[MAXNSLOBJECTNAMELENGTH];
char url[MAXNSLOBJECTNAMELENGTH];
if (resultEntry->result.service == NULL) {
sys_msg(debug, LOG_ERR, "NSLVnode.processAddResult: resultEntry->result.service is NULL; aborting.");
goto Ignore_Entry;
};
serviceNameRef = GetMainStringFromAttribute( resultEntry->result.service, kNSLNameAttrRef );
if (serviceNameRef == NULL) {
sys_msg(debug, LOG_ERR, "NSLVnode.processAddResult: Failed to get serviceNameRef; aborting.");
goto Abort_Service_Entry;
};
servicename[0] = (char)0;
(void)CFStringGetCString(serviceNameRef, servicename, (CFIndex)sizeof(servicename), kCFStringEncodingUTF8);
#if 0
sys_msg(debug, LOG_DEBUG, "NSLVnode.processAddResult: resultEntry = 0x (unsigned long)resultEntry, (unsigned long)serviceNameRef, servicename);
#endif
if (servicename[0] == (char)0) {
sys_msg(debug, LOG_ERR, "NSLVnode.processAddResult: empty servicename string; aborting.");
goto Abort_Service_Entry;
};
targetNameString = [String uniqueString:servicename];
if (targetNameString == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.processAddResult: Failed to allocate targetNameString string; aborting.");
goto Abort_Service_Entry;
}
#if 0 /* DEBUG ONLY */
if (1) {
CFStringRef attributeStringRef = NULL;
char attributeCString[64];
attributeStringRef = GetMainStringFromAttribute( resultEntry->result.service, kNSLProtocolTypeKey );
if (attributeStringRef == NULL) {
sys_msg(debug, LOG_DEBUG, "NSLVnode.processAddResult: kNSLProtocolTypeKey attributeStringRef is NULL.");
goto skip;
};
(void)CFStringGetCString(attributeStringRef, attributeCString, (CFIndex)sizeof(attributeCString), kCFStringEncodingUTF8);
sys_msg(debug, LOG_DEBUG, "NSLVnode.processAddResult: kNSLProtocolTypeKey for '%s' = '%s'...",
servicename,
attributeCString);
skip: ;
};
#endif
v = (NSLVnode *)[self lookupByApparentNameWithoutPopulating:targetNameString];
if (v && ([v getobjectType] != kNetworkServer)) {
[controller destroyVnode:v];
v = nil;
};
if (v == nil) {
urlStringRef = GetMainStringFromAttribute( resultEntry->result.service, kNSLURLAttrRef );
if (urlStringRef) {
(void)CFStringGetCString(urlStringRef, url, (CFIndex)sizeof(url), kCFStringEncodingUTF8);
}
else {
strcpy(url, "<No URL>");
}
sys_msg(debug, LOG_DEBUG, "NSLVnode.processAddResult: Adding server
/* Note: newServiceWithName does not copy the NSL object AGAIN; it just takes over 'ownership' from the caller */
v = [self newServiceWithName:targetNameString service:resultEntry->result.service serviceURL:(urlStringRef ? url : nil)];
if (v == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.processAddResult: Failed to allocate new service node; aborting.");
goto Abort_Service_Entry;
};
resultEntry->result.service = nil; /* NSL object is now 'owned' by newly created Vnode... */
};
[v setGeneration:searchGeneration];
goto Next_Service_Entry;
Abort_Service_Entry:
/* Release any data structures otherwise permanently incorporated into this new node: */
if (v) [v release];
Next_Service_Entry:
/* Release any data structures used to construct this new node: */
if(resultEntry->result.service) CFRelease(resultEntry->result.service);
} else {
sys_msg(debug, LOG_ERR, "NSLVnode.processAddResult: Unknown search target type ( };
if (targetNameString) [targetNameString release];
targetNameString = nil;
Ignore_Entry: ;
}
- (void)processDeleteResult:(struct SearchResult *)resultEntry ofType:(NSLResultType)searchType
{
String *targetNameString = nil;
NSLVnode *v = nil;
sys_msg(debug_nsl, LOG_DEBUG, "NSLVnode.processDeleteResult: Processing kNSLXDeleteResult request...");
if (searchType == kNetworkNeighborhood) {
/*
* Delete a neighborhood entry:
*/
char *neighborhoodname = NULL;
long neighborhoodnamelength;
if (resultEntry->result.neighborhood == NULL) {
sys_msg(debug, LOG_ERR, "NSLVnode.processDeleteResult: resultEntry->result.neighborhood is NULL; aborting.");
goto Ignore_Entry;
};
NSLGetNameFromNeighborhood( resultEntry->result.neighborhood, &neighborhoodname, &neighborhoodnamelength );
if ((neighborhoodname == NULL) || (neighborhoodnamelength == 0)) {
sys_msg(debug, LOG_ERR, "NSLVnode.processDeleteResult: Failed to get neighborhood name for entry; aborting.");
goto Release_Neighborhood;
};
targetNameString = [String uniqueString:neighborhoodname];
if (targetNameString == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.processDeleteResult: Failed to allocate String 'targetNameString'; aborting.");
goto Release_Neighborhood;
}
sys_msg(debug, LOG_DEBUG, "NSLVnode.processDeleteResult: Deleting neighborhood '%s'.", neighborhoodname);
Release_Neighborhood:
/* Release the result data structure: */
NSLFreeNeighborhood(resultEntry->result.neighborhood);
} else if (searchType == kNetworkServer) {
/*
* Delete a server entry:
*/
CFStringRef serviceNameRef;
char servicename[MAXNSLOBJECTNAMELENGTH];
if (resultEntry->result.service == NULL) {
sys_msg(debug, LOG_ERR, "NSLVnode.processDeleteResult: resultEntry->result.service is NULL; aborting.");
goto Ignore_Entry;
};
serviceNameRef = GetMainStringFromAttribute( resultEntry->result.service, kNSLNameAttrRef );
if (serviceNameRef == NULL) {
sys_msg(debug, LOG_ERR, "NSLVnode.processDeleteResult: Failed to get serviceNameRef; aborting.");
goto Release_Server;
};
servicename[0] = (char)0;
(void)CFStringGetCString(serviceNameRef, servicename, (CFIndex)sizeof(servicename), kCFStringEncodingUTF8);
#if 0
sys_msg(debug, LOG_DEBUG, "NSLVnode.processDeleteResult: resultEntry = 0x (unsigned long)resultEntry, (unsigned long)serviceNameRef, servicename);
#endif
if (servicename[0] == (char)0) {
sys_msg(debug, LOG_ERR, "NSLVnode.processDeleteResult: empty servicename string; aborting.");
goto Release_Server;
};
targetNameString = [String uniqueString:servicename];
if (targetNameString == nil) {
sys_msg(debug, LOG_ERR, "NSLVnode.processDeleteResult: Failed to allocate targetNameString string; aborting.");
goto Release_Server;
}
sys_msg(debug, LOG_DEBUG, "NSLVnode.processDeleteResult: Deleting server '%s'.", servicename);
Release_Server:
/* Release the result data structure: */
CFRelease(resultEntry->result.service);
};
if (targetNameString) {
v = (NSLVnode *)[self lookupByApparentNameWithoutPopulating:targetNameString];
if (v) {
[controller destroyVnode:v];
};
[targetNameString release];
};
Ignore_Entry: ;
}
- (BOOL)processSearchResults:(SearchContext *)searchContext
{
struct SearchResultList *results = searchContext->results;
struct SearchResult *resultEntry;
BOOL directoryChanged = NO;
while (1) {
pthread_mutex_lock(&results->resultListMutex);
if (TAILQ_EMPTY(&results->contentsFound)) {
pthread_mutex_unlock(&results->resultListMutex);
break;
};
resultEntry = TAILQ_FIRST(&results->contentsFound);
TAILQ_REMOVE(&results->contentsFound, resultEntry, sr_link);
pthread_mutex_unlock(&results->resultListMutex);
switch (resultEntry->resultType) {
case kNSLXAddResult:
[self processAddResult:resultEntry
ofType:searchContext->searchTargetType
withGeneration:searchContext->searchGenerationNumber];
break;
case kNSLXDeleteResult:
[self processDeleteResult:resultEntry ofType:searchContext->searchTargetType];
break;
case kNSLXPreviousResultsInvalid:
sys_msg(debug, LOG_ERR, "NSLVnode.processSearchResults: Processing kNSLXPreviousResultsInvalid result...");
[self invalidateRecursively:YES notifyFinder:YES];
break;
default:
sys_msg(debug, LOG_ERR, "NSLVnode.processSearchResults: Unknown result type ( break;
}; /* switch (resultEntry->resultType) ... */
directoryChanged = YES;
free(resultEntry);
};
if (directoryChanged) [self markDirectoryChanged];
return directoryChanged;
}
- (Vnode *)lookup:(String *)n
{
[(NSLMap *)[self map] cleanupSearchContextList];
Vnode *vp = [super lookup:n];
if (vp) return vp;
if (strcmp([n value], gInvalidateCue) == 0) {
/* Invalidate the contents of this directory: */
[self invalidateRecursively:NO notifyFinder:NO];
return nil;
} else {
if (gUserLoggedIn) [self populateCompletely:YES]; /* Look up the directory's contents in NSL if necessary */
return [super lookup:n];
};
}
/*
* called from readdir
*/
- (Array *)dirlist
{
/* Note the last time this directory's contents were seen: */
lastSeen.tv_sec = attributes.mtime.seconds;
lastSeen.tv_usec = attributes.mtime.useconds;
[self populateCompletely:NO]; /* Look up the directory's contents in NSL if necessary */
[(NSLMap *)[self map] cleanupSearchContextList];
return [super dirlist];
}
- (int)symlinkWithName:(char *)from to:(char *)to attributes:(struct nfsv2_sattr *)attributes
{
int result;
String *source = nil;
NSLVnode *v = nil;
if (strchr(from, '/')) {
result = NFSERR_NXIO;
goto Error_Exit;
};
source = [String uniqueString:from];
if (havePopulated) {
v = (NSLVnode *)[self lookup:source];
} else {
v = (NSLVnode *)[super lookup:source];
}
if (v) {
result = NFSERR_EXIST;
goto Error_Exit;
}
v = [self newSymlinkWithName:source target:to];
result = (v) ? NFS_OK : NFSERR_NXIO;
Error_Exit:
if (source) [source release];
return result;
}
- (int)remove:(String *)s
{
NSLVnode *child;
/* Find the object they're trying to remove */
if (havePopulated)
child = (NSLVnode *)[self lookup: s];
else
child = (NSLVnode *)[super lookup: s];
if (child == nil)
return NFSERR_NOENT;
/* Only allow removing externally created symlinks */
if (![child fixedEntry])
return NFSERR_PERM;
/* Remove it... */
[controller destroyVnode: child];
return NFS_OK;
}
-(NSLNeighborhood)getNSLNeighborhood
{
if (NSLObjectType == kNetworkNeighborhood) {
return NSLObject.neighborhood;
} else {
sys_msg(debug, LOG_ERR,
"NSLVnode.getNSLNeighborhood: type for [[self apparentName] value], NSLObjectType, kNetworkNeighborhood);
return NULL;
};
}
-(NSLService)getNSLService
{
if (NSLObjectType == kNetworkServer) {
return NSLObject.service;
} else {
sys_msg(debug, LOG_ERR,
"NSLVnode.getNSLService: type for [[self apparentName] value], NSLObjectType, kNetworkServer);
return NULL;
};
}
- (String *)urlString
{
if (urlString) {
return urlString;
} else {
NSLService service = [self getNSLService];
CFStringRef urlStringRef = NULL;
char urlbuffer[MAXNSLOBJECTNAMELENGTH];
urlbuffer[0] = (char)0;
if (service == NULL) {
sys_msg(debug, LOG_ERR, "NSLVnode.urlString: NULL service record for return nil;
};
urlStringRef = GetMainStringFromAttribute( service, kNSLURLAttrRef );
if (urlStringRef == NULL) {
sys_msg(debug, LOG_DEBUG, "NSLVnode.urlString: No URL attribute in service record; calling NSLXResolveService...");
(void)NSLXResolveService(service);
urlStringRef = GetMainStringFromAttribute( service, kNSLURLAttrRef );
};
if (urlStringRef) {
(void)CFStringGetCString(urlStringRef, urlbuffer, (CFIndex)sizeof(urlbuffer), kCFStringEncodingUTF8);
} else {
CFStringRef attributeStringRef = NULL;
char attributeCString[64];
char servicetype[64];
char hostaddress[MAXNSLOBJECTNAMELENGTH];
char portnumber[64];
sys_msg(debug, LOG_DEBUG, "NSLVnode.urlString: reconstituting URL for
attributeStringRef = GetMainStringFromAttribute( service, kNSLServiceTypeAttrRef );
if (attributeStringRef == NULL) {
sys_msg(debug, LOG_DEBUG, "NSLVnode.urlString: kNSLServiceTypeAttrRef attributeStringRef is NULL; aborting.");
goto Error_Exit;
};
(void)CFStringGetCString(attributeStringRef, servicetype, (CFIndex)sizeof(servicetype), kCFStringEncodingUTF8);
// sys_msg(debug, LOG_DEBUG, "NSLVnode.urlString: kNSLServiceTypeAttrRef = '%s'...", servicetype);
if (strcmp(servicetype, "afpovertcp") == 0) {
strcpy(servicetype, "afp");
};
attributeStringRef = GetMainStringFromAttribute( service, kNSLIPAddressAttrRef );
if (attributeStringRef == NULL) {
sys_msg(debug, LOG_DEBUG, "NSLVnode.urlString: kNSLIPAddressAttrRef attributeStringRef is NULL; aborting.");
goto Error_Exit;
};
(void)CFStringGetCString(attributeStringRef, hostaddress, (CFIndex)sizeof(hostaddress), kCFStringEncodingUTF8);
// sys_msg(debug, LOG_DEBUG, "NSLVnode.urlString: hostaddress = '%s'...", hostaddress);
attributeStringRef = GetMainStringFromAttribute( service, kNSLPortAttrRef );
if (attributeStringRef == NULL) {
sys_msg(debug, LOG_DEBUG, "NSLVnode.urlString: kNSLPortAttrRef attributeStringRef is NULL; aborting.");
goto Error_Exit;
};
(void)CFStringGetCString(attributeStringRef, portnumber, (CFIndex)sizeof(portnumber), kCFStringEncodingUTF8);
// sys_msg(debug, LOG_DEBUG, "NSLVnode.urlString: portnumber = '%s'...", portnumber);
snprintf(urlbuffer, sizeof(urlbuffer), " sys_msg(debug, LOG_DEBUG, "NSLVnode.urlString: reconstituted URL = '%s'...", urlbuffer);
};
[self setUrlString:[String uniqueString:urlbuffer]];
};
Error_Exit:
return urlString;
}
- (void)setNfsStatus:(unsigned int)s
{
gettimeofday(&ErrorTime, NULL); /* Mark the time this error was generated */
[super setNfsStatus:s];
}
- (unsigned int)nfsStatus
{
if (nfsStatus) {
struct timeval now;
gettimeofday(&now, NULL);
if (now.tv_sec >= (ErrorTime.tv_sec + NSL_ERROR_PERSISTENCE_DURATION)) {
[self setNfsStatus:0];
};
};
return nfsStatus;
}
/*
* Return YES if the node's server may require authentication.
*
* This should be lightweight because it is called as the result of
* an NFS getattr call. Searching the keychain for an entry
* corresponding to the server would probably be too costly.
*
* Beware that parsing an item's URL can be very expensive, because
* it may take expensive NSL operations to determine the URL (apparently,
* some services are initially populated without a URL).
*/
- (BOOL)needsAuthentication
{
BOOL answer = YES; /* Assume most NSL mount points require authentication. */
NSLService service = NULL;
CFStringRef serviceType = NULL;
service = [self getNSLService];
if (service)
serviceType = GetMainStringFromAttribute( service, kNSLServiceTypeAttrRef );
if (serviceType && CFStringCompare(serviceType, CFSTR("nfs"), kCFCompareCaseInsensitive ) == kCFCompareEqualTo)
answer = NO; /* Assume that NFS never requires authentication. */
return answer;
}
- (BOOL)marked
{
return NO; /* Cause -[Controller validate] to skip NSLVnodes */
}
- (BOOL)mounted
{
/*
* Pretend that ordinary symlinks are always mounted, so the NFS server
* will just return the link without trying to trigger a (non-existent) mount.
*/
return ([self fixedEntry]) ? YES : [super mounted];
}
- (BOOL)checkForUnmount
{
int i, len;
Array *kids;
BOOL result;
kids = [self children];
len = 0;
if (kids != nil)
len = [kids count];
result = NO; /* No unmounts found in this hierarchy yet. */
if (len == 0)
{
if (mounted && ![self fakeMount] && [self server] != nil)
{
/*
* We've found a server marked as mounted. If
* none of its children are mounted any more,
* then mark the server unmounted, and return
* that we found an unmount.
*/
if (!anyChildMounted([[self link] value]))
{
sys_msg(debug, LOG_DEBUG, "Server [self setMounted: NO];
[self resetTime];
if ([self parent] != nil)
[[self parent] resetTime];
result = YES;
}
}
}
else
{
/* Propagate any unmounts from descendants. */
for (i=0; i<len; ++i)
{
if ([[kids objectAtIndex: i] checkForUnmount])
result = YES;
}
}
return result;
}
@end
static String *generateLinkTarget(Vnode *v)
{
size_t len;
char *buf;
String *s;
len = [[[v map] mountPoint] length] + [[v path] length] + 1;
buf = malloc(len);
if (buf == NULL) return nil;
sprintf(buf, " s = [String uniqueString:buf];
free(buf);
return s;
}
static void setupLink(Vnode *v)
{
String *x;
if (v == nil) return;
if ([[v server] isLocalHost])
{
[v setLink:[v source]];
[v setMode:00755 | NFSMODE_LNK];
[v setMounted:YES];
[v setFakeMount:YES];
return;
}
x = generateLinkTarget(v);
if (x == nil) {
sys_msg(debug, LOG_ERR, "setupLink: Failed to allocate link string for '%s'...", [[(NSLVnode *)v apparentName] value]);
goto Error_Exit;
};
sys_msg(debug, LOG_DEBUG, "setupLink: Targeting '%s' to mount on '%s'...", [[(NSLVnode *)v apparentName] value], [x value]);
[v setLink:x];
[x release];
Error_Exit: ;
}
/*
* Return YES if any child of the given path is currently mounted on.
* If there are no children, return NO.
*
* Get the list of mount points. If any mount-on path starts with
* the passed-in path, return YES. Otherwise, return NO.
*/
static BOOL anyChildMounted(const char *path_in)
{
int i, num, pathLen;
struct statfs *fsinfo;
char path[PATH_MAX];
if (realpath(path_in, path) == NULL)
{
sys_msg(debug, LOG_ERR, "Couldn't get real path of return NO;
}
pathLen = strlen(path);
num = getmntinfo(&fsinfo, MNT_NOWAIT);
if (num == 0)
sys_msg(debug, LOG_ERR, "anyChildMounted: getmntinfo failed:
for (i=0; i<num; ++i)
{
if (strncmp(path, fsinfo[i].f_mntonname, pathLen) == 0)
return YES;
}
return NO;
}