git.fiddlerwoaroof.com
Raw Blame History
//  NSData_HMext.m
//
//  Created by John Wiseman on 9/6/05.
//  Copyright 2005 John Wiseman.
//
//  Licensed under the MIT license--see the accompanying LICENSE.txt
//  file.


#import "NSData_HMext.h"
#import "DebugLog.h"


// Reads up to maxLen bytes from a file into a buffer.  The
// buffer is allocated and returned in buf, while the number of
// bytes read is returned in len.
//
// More or less taken from GnuStep's implementation of NSData.

static BOOL readContentsOfFile(NSString* path, void** buf, size_t maxLen, size_t* len, NSZone* zone)
{
    const char	*thePath = 0;
    FILE		*theFile = 0;
    void		*tmp = 0;
    size_t			c;
    size_t		fileLength;
    
    thePath = [path fileSystemRepresentation];
    if (thePath == 0)
    {
        //      NSWarnFLog(@"Open (%@) attempt failed - bad path", path);
        return NO;
    }
    
    theFile = fopen(thePath, "rb");
    
    if (theFile == 0)		/* We failed to open the file. */
    {
        //      NSWarnFLog(@"Open (%@) attempt failed - %s", path,
        //      GSLastErrorStr(errno));
        goto failure;
    }
    
    /*
     *	Seek to the end of the file.
     */
    c = fseek(theFile, 0L, SEEK_END);
    if (c != 0)
    {
        //      NSWarnFLog(@"Seek to end of file (%@) failed - %s", path,
        //      GSLastErrorStr(errno));
        goto failure;
    }
    
    /*
     *	Determine the length of the file (having seeked to the end of the
     *	file) by calling ftell().
     */
    fileLength = ftell(theFile);
    if (fileLength == -1)
    {
        //      NSWarnFLog(@"Ftell on %@ failed - %s", path,
        //      GSLastErrorStr(errno));
        goto failure;
    }
    
    /*
     *	Rewind the file pointer to the beginning, preparing to read in
     *	the file.
     */
    c = fseek(theFile, 0L, SEEK_SET);
    if (c != 0)
    {
        //      NSWarnFLog(@"Fseek to start of file (%@) failed - %s", path,
        //      GSLastErrorStr(errno));
        goto failure;
    }
    
    if (fileLength == 0)
    {
        unsigned char	buf[BUFSIZ];
        size_t bytesToRead = maxLen;
        /*
         * Special case ... a file of length zero may be a named pipe or some
         * file in the /proc filesystem, which will return us data if we read
         * from it ... so we try reading as much as we can, up to the specified
         * limit.
         */
        while ((c = fread(buf, 1, (bytesToRead < BUFSIZ) ? bytesToRead : BUFSIZ, theFile)) != 0)
        {
            if (tmp == 0)
            {
                tmp = NSZoneMalloc(zone, c);
            }
            else
            {
                tmp = NSZoneRealloc(zone, tmp, fileLength + c);
            }
            if (tmp == 0)
            {
                //	      NSLog(@"Malloc failed for file (%@) of length %d - %s", path,
                //		fileLength + c, GSLastErrorStr(errno));
                goto failure;
            }
            memcpy(tmp + fileLength, buf, c);
            fileLength += c;
            bytesToRead -= c;
        }
        if (fileLength == maxLen)
        {
            DebugLog(DEBUG_LEVEL_DEBUG, @"Truncated indexing of %s to %d bytes", thePath, maxLen);
        }
    }
    else
    {
        if (fileLength > maxLen)
        {
            fileLength = maxLen;
            DebugLog(DEBUG_LEVEL_DEBUG, @"Truncated indexing of %s to %d bytes", thePath, maxLen);
        }
        tmp = NSZoneMalloc(zone, fileLength);
        if (tmp == 0)
        {
            //	  NSLog(@"Malloc failed for file (%@) of length %d - %s", path,
            //	  fileLength, GSLastErrorStr(errno));
            goto failure;
        }
        
        c = fread(tmp, 1, fileLength, theFile);
        if (c != (int)fileLength)
        {
            //	  NSWarnFLog(@"read of file (%@) contents failed - %s", path,
            //	  GSLastErrorStr(errno));
            goto failure;
        }
    }
    
    *buf = tmp;
    *len = fileLength;
    fclose(theFile);
    return YES;
    
    /*
     *	Just in case the failure action needs to be changed.
     */
failure:
    if (tmp != 0)
    {
        NSZoneFree(zone, tmp);
    }
    if (theFile != 0)
    {
        fclose(theFile);
    }
    return NO;
}

@implementation NSData (NSData_Extensions)


/**
 * Returns a data object encapsulating the contents of the specified file
 * (but only up to the first theMaxSize bytes of the file).
 * Invokes -initWithContentsOfFile:
 *
 * Based on GnuStep's -[NSData dataWithContentsOfFile].
 */
+ (id) dataWithContentsOfFile: (NSString*)path maxSize:(int)theMaxSize error:(NSError**)error
{
    NSData	*d;
    
    d = [NSData allocWithZone: NSDefaultMallocZone()];
    d = [d initWithContentsOfFile: path maxSize:theMaxSize error:error];
    return [d autorelease];
}


/**
 * Initializes the receiver with up to theMaxSize bytes of the specified file.
 * Returns the resulting object.
 * Returns nil if the file does not exist or can not be read for some reason,
 * in which case error information will (probably) be returned as well.
 *
 * Based on GnuStep's -[NSData initWithContentsOfFile].
 */
- (id) initWithContentsOfFile: (NSString*)path maxSize:(int)theMaxSize error:(NSError**)error
{
    void		*fileBytes = NULL;
    size_t	fileLength = 0;
    NSZone	*zone;
    
    zone = NSDefaultMallocZone();
    if (readContentsOfFile(path, &fileBytes, theMaxSize, &fileLength, zone) == NO)
    {
        if (error)
        {
            NSNumber *errorCode = [NSNumber numberWithInt:errno];
            NSString *errorDescription = [NSString stringWithCString:strerror(errno) encoding:NSUTF8StringEncoding];
            NSString* errorPath = path;
            NSMutableDictionary *errorAttribs = [NSMutableDictionary dictionaryWithCapacity:2];
            [errorAttribs setObject:errorCode forKey:@"Errno"];
            [errorAttribs setObject:errorDescription forKey:@"Description"];
            [errorAttribs setObject:errorPath forKey:@"Path"];
            *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:errorAttribs];
        }
        [self dealloc];
        return nil;
    }
    else
    {
        self = [self initWithBytesNoCopy:fileBytes length:fileLength freeWhenDone:YES];
    }
    return self;
}

@end