mirror of https://github.com/fadden/nulib2.git
663 lines
20 KiB
C
663 lines
20 KiB
C
/*
|
|
* NuLib2
|
|
* Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
|
|
* This is free software; you can redistribute it and/or modify it under the
|
|
* terms of the BSD License, see the file COPYING.
|
|
*
|
|
* Filename manipulation, including file type preservation.
|
|
*/
|
|
#include "NuLib2.h"
|
|
#include <ctype.h>
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* Common definitions
|
|
* ===========================================================================
|
|
*/
|
|
|
|
#define kPreserveIndic '#' /* use # rather than $ for hex indication */
|
|
#define kFilenameExtDelim '.' /* separates extension from filename */
|
|
#define kResourceFlag 'r'
|
|
#define kDiskImageFlag 'i'
|
|
#define kMaxExtLen 5 /* ".1234" */
|
|
#define kResourceStr "_rsrc_"
|
|
|
|
/* must be longer then strlen(kResourceStr)... no problem there */
|
|
#define kMaxPathGrowth (sizeof("#XXXXXXXXYYYYYYYYZ")-1 + kMaxExtLen+1)
|
|
|
|
|
|
/* ProDOS file type names; must be entirely in upper case */
|
|
static const char gFileTypeNames[256][4] = {
|
|
"NON", "BAD", "PCD", "PTX", "TXT", "PDA", "BIN", "FNT",
|
|
"FOT", "BA3", "DA3", "WPF", "SOS", "$0D", "$0E", "DIR",
|
|
"RPD", "RPI", "AFD", "AFM", "AFR", "SCL", "PFS", "$17",
|
|
"$18", "ADB", "AWP", "ASP", "$1C", "$1D", "$1E", "$1F",
|
|
"TDM", "$21", "$22", "$23", "$24", "$25", "$26", "$27",
|
|
"$28", "$29", "8SC", "8OB", "8IC", "8LD", "P8C", "$2F",
|
|
"$30", "$31", "$32", "$33", "$34", "$35", "$36", "$37",
|
|
"$38", "$39", "$3A", "$3B", "$3C", "$3D", "$3E", "$3F",
|
|
"DIC", "OCR", "FTD", "$43", "$44", "$45", "$46", "$47",
|
|
"$48", "$49", "$4A", "$4B", "$4C", "$4D", "$4E", "$4F",
|
|
"GWP", "GSS", "GDB", "DRW", "GDP", "HMD", "EDU", "STN",
|
|
"HLP", "COM", "CFG", "ANM", "MUM", "ENT", "DVU", "FIN",
|
|
"$60", "$61", "$62", "$63", "$64", "$65", "$66", "$67",
|
|
"$68", "$69", "$6A", "BIO", "$6C", "TDR", "PRE", "HDV",
|
|
"$70", "$71", "$72", "$73", "$74", "$75", "$76", "$77",
|
|
"$78", "$79", "$7A", "$7B", "$7C", "$7D", "$7E", "$7F",
|
|
"$80", "$81", "$82", "$83", "$84", "$85", "$86", "$87",
|
|
"$88", "$89", "$8A", "$8B", "$8C", "$8D", "$8E", "$8F",
|
|
"$90", "$91", "$92", "$93", "$94", "$95", "$96", "$97",
|
|
"$98", "$99", "$9A", "$9B", "$9C", "$9D", "$9E", "$9F",
|
|
"WP ", "$A1", "$A2", "$A3", "$A4", "$A5", "$A6", "$A7",
|
|
"$A8", "$A9", "$AA", "GSB", "TDF", "BDF", "$AE", "$AF",
|
|
"SRC", "OBJ", "LIB", "S16", "RTL", "EXE", "PIF", "TIF",
|
|
"NDA", "CDA", "TOL", "DVR", "LDF", "FST", "$BE", "DOC",
|
|
"PNT", "PIC", "ANI", "PAL", "$C4", "OOG", "SCR", "CDV",
|
|
"FON", "FND", "ICN", "$CB", "$CC", "$CD", "$CE", "$CF",
|
|
"$D0", "$D1", "$D2", "$D3", "$D4", "MUS", "INS", "MDI",
|
|
"SND", "$D9", "$DA", "DBM", "$DC", "DDD", "$DE", "$DF",
|
|
"LBR", "$E1", "ATK", "$E3", "$E4", "$E5", "$E6", "$E7",
|
|
"$E8", "$E9", "$EA", "$EB", "$EC", "$ED", "R16", "PAS",
|
|
"CMD", "$F1", "$F2", "$F3", "$F4", "$F5", "$F6", "$F7",
|
|
"$F8", "OS ", "INT", "IVR", "BAS", "VAR", "REL", "SYS"
|
|
};
|
|
|
|
/*
|
|
* Some file extensions we recognize. When adding files with "extended"
|
|
* preservation mode, we try to assign types to files that weren't
|
|
* explicitly preserved, but nevertheless have a recognizeable type.
|
|
*
|
|
* geoff @ gwlink.net points out that this really ought to be in an external
|
|
* file rather than a hard-coded table. Ought to fix that someday.
|
|
*/
|
|
static const struct {
|
|
const char* label;
|
|
uint8_t fileType;
|
|
uint16_t auxType;
|
|
} gRecognizedExtensions[] = {
|
|
{ "ASM", 0xb0, 0x0003 }, /* APW assembly source */
|
|
{ "C", 0xb0, 0x000a }, /* APW C source */
|
|
{ "H", 0xb0, 0x000a }, /* APW C header */
|
|
{ "CPP", 0xb0, 0x0000 }, /* generic source file */
|
|
{ "BNY", 0xe0, 0x8000 }, /* Binary II lib */
|
|
{ "BQY", 0xe0, 0x8000 }, /* Binary II lib, w/ compress */
|
|
{ "BXY", 0xe0, 0x8000 }, /* Binary II wrap around SHK */
|
|
{ "BSE", 0xe0, 0x8000 }, /* Binary II wrap around SEA */
|
|
{ "SEA", 0xb3, 0xdb07 }, /* GSHK SEA */
|
|
{ "TEXT", 0x04, 0x0000 }, /* ASCII text */
|
|
{ "GIF", 0xc0, 0x8006 }, /* GIF image */
|
|
{ "JPG", 0x06, 0x0000 }, /* JPEG (nicer than 'NON') */
|
|
{ "JPEG", 0x06, 0x0000 }, /* JPEG (nicer than 'NON') */
|
|
{ "SHK", 0xe0, 0x8002 }, /* ShrinkIt archive */
|
|
};
|
|
|
|
|
|
/*
|
|
* Return a pointer to the three-letter representation of the file type name.
|
|
*/
|
|
const char* GetFileTypeString(uint32_t fileType)
|
|
{
|
|
if (fileType < NELEM(gFileTypeNames))
|
|
return gFileTypeNames[fileType];
|
|
else
|
|
return "???";
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* File type preservation
|
|
* ===========================================================================
|
|
*/
|
|
|
|
/*
|
|
* Add a preservation string.
|
|
*
|
|
* "pathBuf" is assumed to have enough space to hold the current path
|
|
* plus kMaxPathGrowth more. It will be modified in place.
|
|
*/
|
|
static void AddPreservationString(NulibState* pState,
|
|
const NuPathnameProposal* pPathProposal, char* pathBuf)
|
|
{
|
|
char extBuf[kMaxPathGrowth +1];
|
|
const NuRecord* pRecord;
|
|
const NuThread* pThread;
|
|
NuThreadID threadID;
|
|
char* cp;
|
|
|
|
Assert(pState != NULL);
|
|
Assert(pPathProposal != NULL);
|
|
Assert(pathBuf != NULL);
|
|
Assert(NState_GetModPreserveType(pState));
|
|
|
|
pRecord = pPathProposal->pRecord;
|
|
pThread = pPathProposal->pThread;
|
|
Assert(pRecord != NULL);
|
|
Assert(pThread != NULL);
|
|
|
|
cp = extBuf;
|
|
|
|
/*
|
|
* Cons up a preservation string. On some platforms "sprintf" doesn't
|
|
* return the #of characters written, so we add it up manually.
|
|
*/
|
|
if (pRecord->recFileType < 0x100 && pRecord->recExtraType < 0x10000) {
|
|
sprintf(cp, "%c%02x%04x", kPreserveIndic, pRecord->recFileType,
|
|
pRecord->recExtraType);
|
|
cp += 7;
|
|
} else {
|
|
sprintf(cp, "%c%08x%08x", kPreserveIndic, pRecord->recFileType,
|
|
pRecord->recExtraType);
|
|
cp += 17;
|
|
}
|
|
|
|
threadID = NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind);
|
|
if (threadID == kNuThreadIDRsrcFork)
|
|
*cp++ = kResourceFlag;
|
|
else if (threadID == kNuThreadIDDiskImage)
|
|
*cp++ = kDiskImageFlag;
|
|
|
|
/*
|
|
* If they've asked for "extended" type preservation, then we need
|
|
* to retain either the existing extension or append an extension
|
|
* based on the ProDOS file type.
|
|
*/
|
|
if (NState_GetModPreserveTypeExtended(pState)) {
|
|
const char* pExt;
|
|
char* end;
|
|
|
|
/*
|
|
* Find extension. Note FindExtension guarantees there's at least
|
|
* one char after '.'.
|
|
*
|
|
* It's hard to know when this is right and when it isn't. It's
|
|
* fairly likely that a text file really ought to end in ".txt",
|
|
* and it's fairly unlikely that a BIN file should be ".bin", but
|
|
* where do the rest fall in? We might want to force TXT files
|
|
* to be ".txt", and perhaps do something clever for some others.
|
|
*/
|
|
if (pRecord->recFileType == 0x04 || threadID == kNuThreadIDDiskImage)
|
|
pExt = NULL;
|
|
else
|
|
pExt = FindExtension(pState, pathBuf);
|
|
if (pExt != NULL) {
|
|
pExt++; /* skip past the '.' */
|
|
|
|
if (strlen(pExt) >= kMaxExtLen) {
|
|
/* too long, forget it */
|
|
pExt = NULL;
|
|
} else {
|
|
/* if strictly decimal-numeric, don't use it (.1, .2, etc) */
|
|
(void) strtoul(pExt, &end, 10);
|
|
if (*end == '\0') {
|
|
pExt = NULL;
|
|
} else {
|
|
/* if '#' appears in it, don't use it -- it'll confuse us */
|
|
const char* ccp = pExt;
|
|
while (*ccp != '\0') {
|
|
if (*ccp == '#') {
|
|
pExt = NULL;
|
|
break;
|
|
}
|
|
ccp++;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
/*
|
|
* There's no extension on the filename. Use the standard
|
|
* ProDOS type, if one exists for this entry. We don't use
|
|
* the table if it's "NON" or a hex value.
|
|
*/
|
|
if (threadID == kNuThreadIDDiskImage)
|
|
pExt = "PO"; /* indicate it's a ProDOS-ordered image */
|
|
else if (pRecord->recFileType) {
|
|
pExt = GetFileTypeString(pRecord->recFileType);
|
|
if (pExt[0] == '?' || pExt[0] == '$')
|
|
pExt = NULL;
|
|
}
|
|
}
|
|
|
|
if (pExt != NULL) {
|
|
*cp++ = kFilenameExtDelim;
|
|
strcpy(cp, pExt);
|
|
cp += strlen(pExt);
|
|
}
|
|
}
|
|
|
|
/* make sure it's terminated */
|
|
*cp = '\0';
|
|
|
|
Assert(cp - extBuf <= kMaxPathGrowth);
|
|
strcat(pathBuf, extBuf);
|
|
}
|
|
|
|
/*
|
|
* Normalize a path for the conventions on the output filesystem. This
|
|
* adds optional file type preservation.
|
|
*
|
|
* The path from the archive is in "pPathProposal". Thew new pathname
|
|
* will be placed in the "new pathname" section of "pPathProposal".
|
|
*
|
|
* The new pathname may be shorter (because characters were removed) or
|
|
* longer (if we add a "#XXYYYYZ" extension or replace chars with '%' codes).
|
|
*
|
|
* This returns the new pathname, which is held in NulibState's temporary
|
|
* pathname buffer.
|
|
*/
|
|
const char* NormalizePath(NulibState* pState, NuPathnameProposal* pPathProposal)
|
|
{
|
|
NuError err = kNuErrNone;
|
|
char* pathBuf;
|
|
const char* startp;
|
|
const char* endp;
|
|
char* dstp;
|
|
char localFssep;
|
|
int newBufLen;
|
|
|
|
Assert(pState != NULL);
|
|
Assert(pPathProposal != NULL);
|
|
Assert(pPathProposal->pathnameUNI != NULL);
|
|
|
|
localFssep = NState_GetSystemPathSeparator(pState);
|
|
|
|
/*
|
|
* Set up temporary buffer space. The maximum possible expansion
|
|
* requires converting all chars to '%' codes and adding the longest
|
|
* possible preservation string.
|
|
*/
|
|
newBufLen = strlen(pPathProposal->pathnameUNI)*3 + kMaxPathGrowth +1;
|
|
NState_SetTempPathnameLen(pState, newBufLen);
|
|
pathBuf = NState_GetTempPathnameBuf(pState);
|
|
Assert(pathBuf != NULL);
|
|
if (pathBuf == NULL)
|
|
return NULL;
|
|
|
|
startp = pPathProposal->pathnameUNI;
|
|
dstp = pathBuf;
|
|
while (*startp == pPathProposal->filenameSeparator) {
|
|
/* ignore leading path sep; always extract to current dir */
|
|
startp++;
|
|
}
|
|
|
|
/* normalize all directory components and the filename component */
|
|
while (startp != NULL) {
|
|
endp = strchr(startp, pPathProposal->filenameSeparator);
|
|
if (endp != NULL) {
|
|
/* normalize directory component */
|
|
err = NormalizeDirectoryName(pState, startp, endp - startp,
|
|
pPathProposal->filenameSeparator, &dstp,
|
|
NState_GetTempPathnameLen(pState));
|
|
if (err != kNuErrNone)
|
|
goto bail;
|
|
|
|
*dstp++ = localFssep;
|
|
|
|
startp = endp +1;
|
|
} else {
|
|
/* normalize filename */
|
|
err = NormalizeFileName(pState, startp, strlen(startp),
|
|
pPathProposal->filenameSeparator, &dstp,
|
|
NState_GetTempPathnameLen(pState));
|
|
if (err != kNuErrNone)
|
|
goto bail;
|
|
|
|
/* add/replace extension if necessary */
|
|
*dstp++ = '\0';
|
|
if (NState_GetModPreserveType(pState)) {
|
|
AddPreservationString(pState, pPathProposal, pathBuf);
|
|
} else if (NuGetThreadID(pPathProposal->pThread) == kNuThreadIDRsrcFork)
|
|
{
|
|
#ifndef HAS_RESOURCE_FORKS
|
|
/* add this in lieu of the preservation extension */
|
|
strcat(pathBuf, kResourceStr);
|
|
#endif
|
|
}
|
|
|
|
startp = NULL; /* we're done */
|
|
}
|
|
}
|
|
|
|
pPathProposal->newPathnameUNI = pathBuf;
|
|
pPathProposal->newFilenameSeparator = localFssep;
|
|
|
|
/* check for overflow */
|
|
Assert(dstp - pathBuf <= newBufLen);
|
|
|
|
/*
|
|
* If "junk paths" is set, drop everything but the last component.
|
|
*/
|
|
if (NState_GetModJunkPaths(pState)) {
|
|
char* lastFssep;
|
|
lastFssep = strrchr(pathBuf, localFssep);
|
|
if (lastFssep != NULL) {
|
|
Assert(*(lastFssep+1) != '\0'); /* should already have been caught*/
|
|
memmove(pathBuf, lastFssep+1, strlen(lastFssep+1)+1);
|
|
}
|
|
}
|
|
|
|
bail:
|
|
if (err != kNuErrNone)
|
|
return NULL;
|
|
return pathBuf;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* File type restoration
|
|
* ===========================================================================
|
|
*/
|
|
|
|
/*
|
|
* Try to figure out what file type is associated with a filename extension.
|
|
*
|
|
* This checks the standard list of ProDOS types (which should catch things
|
|
* like "TXT" and "BIN") and the separate list of recognized extensions.
|
|
*/
|
|
static void LookupExtension(NulibState* pState, const char* ext,
|
|
uint32_t* pFileType, uint32_t* pAuxType)
|
|
{
|
|
char uext3[4];
|
|
int i, extLen;
|
|
|
|
extLen = strlen(ext);
|
|
Assert(extLen > 0);
|
|
|
|
/*
|
|
* First step is to try to find it in the recognized types list.
|
|
*/
|
|
for (i = 0; i < NELEM(gRecognizedExtensions); i++) {
|
|
if (strcasecmp(ext, gRecognizedExtensions[i].label) == 0) {
|
|
*pFileType = gRecognizedExtensions[i].fileType;
|
|
*pAuxType = gRecognizedExtensions[i].auxType;
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Second step is to try to find it in the ProDOS types list.
|
|
*
|
|
* The extension is converted to upper case and padded with spaces.
|
|
*
|
|
* [do we want to obstruct matching on things like '$f7' here?]
|
|
*/
|
|
if (extLen <= 3) {
|
|
for (i = 2; i >= extLen; i--)
|
|
uext3[i] = ' ';
|
|
for ( ; i >= 0; i--)
|
|
uext3[i] = toupper(ext[i]);
|
|
uext3[3] = '\0';
|
|
|
|
/*printf("### converted '%s' to '%s'\n", ext, uext3);*/
|
|
|
|
for (i = 0; i < NELEM(gFileTypeNames); i++) {
|
|
if (strcmp(uext3, gFileTypeNames[i]) == 0) {
|
|
*pFileType = i;
|
|
goto bail;
|
|
}
|
|
}
|
|
}
|
|
|
|
bail:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Try to associate some meaning with the file extension.
|
|
*/
|
|
void InterpretExtension(NulibState* pState, const char* pathName,
|
|
uint32_t* pFileType, uint32_t* pAuxType)
|
|
{
|
|
const char* pExt;
|
|
|
|
Assert(pState != NULL);
|
|
Assert(pathName != NULL);
|
|
Assert(pFileType != NULL);
|
|
Assert(pAuxType != NULL);
|
|
|
|
pExt = FindExtension(pState, pathName);
|
|
if (pExt != NULL)
|
|
LookupExtension(pState, pExt+1, pFileType, pAuxType);
|
|
}
|
|
|
|
|
|
/*
|
|
* Check to see if there's a preservation string on the filename. If so,
|
|
* set the filetype and auxtype information, and trim the preservation
|
|
* string off.
|
|
*
|
|
* We have to be careful not to trip on false-positive occurrences of '#'
|
|
* in the filename.
|
|
*/
|
|
Boolean ExtractPreservationString(NulibState* pState, char* pathname,
|
|
uint32_t* pFileType, uint32_t* pAuxType, NuThreadID* pThreadID)
|
|
{
|
|
char numBuf[9];
|
|
uint32_t fileType, auxType;
|
|
NuThreadID threadID;
|
|
char* pPreserve;
|
|
char* cp;
|
|
int digitCount;
|
|
|
|
Assert(pState != NULL);
|
|
Assert(pathname != NULL);
|
|
Assert(pFileType != NULL);
|
|
Assert(pAuxType != NULL);
|
|
Assert(pThreadID != NULL);
|
|
|
|
pPreserve = strrchr(pathname, kPreserveIndic);
|
|
if (pPreserve == NULL)
|
|
return false;
|
|
|
|
/* count up the #of hex digits */
|
|
digitCount = 0;
|
|
for (cp = pPreserve+1; *cp != '\0' && isxdigit((int)*cp); cp++)
|
|
digitCount++;
|
|
|
|
/* extract the file and aux type */
|
|
switch (digitCount) {
|
|
case 6:
|
|
/* ProDOS 1-byte type and 2-byte aux */
|
|
memcpy(numBuf, pPreserve+1, 2);
|
|
numBuf[2] = 0;
|
|
fileType = strtoul(numBuf, &cp, 16);
|
|
Assert(cp == numBuf + 2);
|
|
|
|
auxType = strtoul(pPreserve+3, &cp, 16);
|
|
Assert(cp == pPreserve + 7);
|
|
break;
|
|
case 16:
|
|
/* HFS 4-byte type and 4-byte creator */
|
|
memcpy(numBuf, pPreserve+1, 8);
|
|
numBuf[8] = 0;
|
|
fileType = strtoul(numBuf, &cp, 16);
|
|
Assert(cp == numBuf + 8);
|
|
|
|
auxType = strtoul(pPreserve+9, &cp, 16);
|
|
Assert(cp == pPreserve + 17);
|
|
break;
|
|
default:
|
|
/* not valid */
|
|
return false;
|
|
}
|
|
|
|
/* check for a threadID specifier */
|
|
threadID = kNuThreadIDDataFork;
|
|
switch (*cp) {
|
|
case kResourceFlag:
|
|
threadID = kNuThreadIDRsrcFork;
|
|
cp++;
|
|
break;
|
|
case kDiskImageFlag:
|
|
threadID = kNuThreadIDDiskImage;
|
|
cp++;
|
|
break;
|
|
default:
|
|
/* do nothing... yet */
|
|
break;
|
|
}
|
|
|
|
/* make sure we were the very last component */
|
|
switch (*cp) {
|
|
case kFilenameExtDelim: /* redundant "-ee" extension */
|
|
case '\0': /* end of string! */
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
/* truncate the original string, and return what we got */
|
|
*pPreserve = '\0';
|
|
*pFileType = fileType;
|
|
*pAuxType = auxType;
|
|
*pThreadID = threadID;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove NuLib2's normalization magic (e.g. "%2f" for '/').
|
|
*
|
|
* This always results in the filename staying the same length or getting
|
|
* smaller, so we can do it in place in the buffer.
|
|
*/
|
|
void DenormalizePath(NulibState* pState, char* pathBuf)
|
|
{
|
|
const char* srcp;
|
|
char* dstp;
|
|
char ch;
|
|
|
|
srcp = pathBuf;
|
|
dstp = pathBuf;
|
|
|
|
while (*srcp != '\0') {
|
|
if (*srcp == kForeignIndic) {
|
|
srcp++;
|
|
if (*srcp == kForeignIndic) {
|
|
*dstp++ = kForeignIndic;
|
|
srcp++;
|
|
} else if (isxdigit((int)*srcp)) {
|
|
ch = HexDigit(*srcp) << 4;
|
|
srcp++;
|
|
if (isxdigit((int)*srcp)) {
|
|
/* valid, output char */
|
|
ch += HexDigit(*srcp);
|
|
if (ch != '\0') /* used by Win32 converter */
|
|
*dstp++ = ch;
|
|
srcp++;
|
|
} else {
|
|
/* bogus '%' with trailing hex digit found! */
|
|
*dstp++ = kForeignIndic;
|
|
*dstp++ = *(srcp-1);
|
|
}
|
|
} else {
|
|
/* bogus lone '%s' found! */
|
|
*dstp++ = kForeignIndic;
|
|
}
|
|
|
|
} else {
|
|
*dstp++ = *srcp++;
|
|
}
|
|
}
|
|
|
|
*dstp = '\0';
|
|
Assert(dstp <= srcp);
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* Misc utils
|
|
* ===========================================================================
|
|
*/
|
|
|
|
/*
|
|
* Find the filename component of a local pathname. Uses the fssep defined
|
|
* in pState.
|
|
*
|
|
* Always returns a pointer to a string; never returns NULL.
|
|
*/
|
|
const char* FilenameOnly(NulibState* pState, const char* pathname)
|
|
{
|
|
const char* retstr;
|
|
const char* pSlash;
|
|
char* tmpStr = NULL;
|
|
|
|
Assert(pState != NULL);
|
|
Assert(pathname != NULL);
|
|
|
|
pSlash = strrchr(pathname, NState_GetSystemPathSeparator(pState));
|
|
if (pSlash == NULL) {
|
|
retstr = pathname; /* whole thing is the filename */
|
|
goto bail;
|
|
}
|
|
|
|
pSlash++;
|
|
if (*pSlash == '\0') {
|
|
if (strlen(pathname) < 2) {
|
|
retstr = pathname; /* the pathname is just "/"? Whatever */
|
|
goto bail;
|
|
}
|
|
|
|
/* some bonehead put an fssep on the very end; back up before it */
|
|
/* (not efficient, but this should be rare, and I'm feeling lazy) */
|
|
tmpStr = strdup(pathname);
|
|
tmpStr[strlen(pathname)-1] = '\0';
|
|
pSlash = strrchr(tmpStr, NState_GetSystemPathSeparator(pState));
|
|
|
|
if (pSlash == NULL) {
|
|
retstr = pathname; /* just a filename with a '/' after it */
|
|
goto bail;
|
|
}
|
|
|
|
pSlash++;
|
|
if (*pSlash == '\0') {
|
|
retstr = pathname; /* I give up! */
|
|
goto bail;
|
|
}
|
|
|
|
retstr = pathname + (pSlash - tmpStr);
|
|
|
|
} else {
|
|
retstr = pSlash;
|
|
}
|
|
|
|
bail:
|
|
Free(tmpStr);
|
|
return retstr;
|
|
}
|
|
|
|
/*
|
|
* Return the filename extension found in a full pathname.
|
|
*
|
|
* An extension is the stuff following the last '.' in the filename. If
|
|
* there is nothing following the last '.', then there is no extension.
|
|
*
|
|
* Returns a pointer to the '.' preceding the extension, or NULL if no
|
|
* extension was found.
|
|
*/
|
|
const char* FindExtension(NulibState* pState, const char* pathname)
|
|
{
|
|
const char* pFilename;
|
|
const char* pExt;
|
|
|
|
/*
|
|
* We have to isolate the filename so that we don't get excited
|
|
* about "/foo.bar/file".
|
|
*/
|
|
pFilename = FilenameOnly(pState, pathname);
|
|
Assert(pFilename != NULL);
|
|
pExt = strrchr(pFilename, kFilenameExtDelim);
|
|
|
|
/* also check for "/blah/foo.", which doesn't count */
|
|
if (pExt != NULL && *(pExt+1) != '\0')
|
|
return pExt;
|
|
|
|
return NULL;
|
|
}
|
|
|