/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* ** Copyright (c) 2013 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** This file contains routines related to working with the filesystem. */ #include "fossil-scm/fossil-internal.h" #include #include /* strlen() */ #include /* NULL on linux */ #include #ifdef _WIN32 # include # include # include #else # include /* access(2) */ # include # include # include #endif #include /* Only for debugging */ #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) FILE *fsl_fopen(const char *zName, const char *zMode){ FILE *f; if(zName && ('-'==*zName && !zName[1])){ f = (strchr(zMode, 'w') || strchr(zMode,'+')) ? stdout : stdin ; }else{ #ifdef _WIN32 wchar_t *uMode; wchar_t *uName; f = _wfopen(uName, uMode); fsl_filename_free(uName); fsl_unicode_free(uMode); #else f = fopen(zName, zMode); #endif } return f; } void fsl_fclose( FILE * f ){ if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){ fclose(f); } } /* ** Wrapper around the access() system call. */ int fsl_file_access(const char *zFilename, int flags){ #ifdef _WIN32 wchar_t *zMbcs = (wchar_t *)fsl_utf8_to_filename(zFilename); #define ACC _waccess #else char *zMbcs = (char*)fsl_utf8_to_filename(zFilename); #define ACC access #endif int rc = zMbcs ? ACC(zMbcs, flags) : FSL_RC_OOM; if(zMbcs) fsl_filename_free(zMbcs); return rc; #undef ACC } int fsl_getcwd(char *zBuf, fsl_size_t nBuf, fsl_size_t * outLen){ #ifdef _WIN32 char *zPwdUtf8; fsl_size_t nPwd; fsl_size_t i; wchar_t zPwd[2000]; if(!zBuf) return FSL_RC_MISUSE; else if(!nBuf) return FSL_RC_RANGE; /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx It says: Note File I/O functions in the Windows API convert "/" to "\" as part of converting the name to an NT-style name, except when using the "\\?\" prefix as detailed in the following sections. So the path-demangling bits below might do more damage they fix? */ else if( _wgetcwd(zPwd, sizeof(zPwd)/sizeof(zPwd[0])-1)==0 ){ /* FIXME: how to determine if FSL_RC_RANGE is a better return value? */ return FSL_RC_IO; } zPwdUtf8 = fsl_filename_to_utf8(zPwd); if(!zPwdUtf8) return FSL_RC_OOM; nPwd = strlen(zPwdUtf8); if( nPwd > nBuf-1 ){ fsl_filename_free(zPwdUtf8); return FSL_RC_RANGE; } for(i=0; zPwdUtf8[i]; i++) if( zPwdUtf8[i]=='\\' ) zPwdUtf8[i] = '/'; memcpy(zBuf, zPwdUtf8, nPwd+1); fsl_filename_free(zPwdUtf8); if(outLen) *outLen = nPwd; return 0; #else if(!zBuf) return FSL_RC_MISUSE; else if(!nBuf) return FSL_RC_RANGE; else if( getcwd(zBuf, nBuf /*-1 not necessary: getcwd() NUL-terminates*/)==0 ){ return fsl_errno_to_rc(errno, FSL_RC_IO); }else{ if(outLen) *outLen = fsl_strlen(zBuf); return 0; } #endif } /* ** The file status information from the most recent stat() call. ** ** Use _stati64 rather than stat on windows, in order to handle files ** larger than 2GB. */ #if defined(_WIN32) && (defined(__MSVCRT__) || defined(_MSC_VER)) # undef stat # define stat _stati64 #endif /* ** On Windows S_ISLNK always returns FALSE. */ #if !defined(S_ISLNK) # define S_ISLNK(x) (0) #endif /* Reminder: the semmantics of the 3rd parameter are reversed from v1's fossil_stat(). */ int fsl_stat(const char *zFilename, fsl_fstat * fst, char derefSymlinks){ if(!zFilename || !fst) return FSL_RC_MISUSE; else if(!*zFilename) return FSL_RC_RANGE; else{ int rc; struct stat buf; #if !defined(_WIN32) char *zMbcs = (char *)fsl_utf8_to_filename(zFilename); if(!zMbcs) rc = FSL_RC_OOM; else{ if( derefSymlinks ){ rc = stat(zMbcs, &buf); }else{ rc = lstat(zMbcs, &buf); } } #else wchar_t *zMbcs = (wchar_t *)fsl_utf8_to_filename(zFilename); rc = zMbcs ? _wstati64(zMbcs, &buf) : FSL_RC_OOM; #endif if(zMbcs) fsl_filename_free(zMbcs); if(0==rc){ *fst = fsl_fstat_empty; fst->ctime = (fsl_time_t)buf.st_ctime; fst->mtime = (fsl_time_t)buf.st_mtime; fst->size = (fsl_size_t)buf.st_size; if(S_ISDIR(buf.st_mode)) fst->type = FSL_FSTAT_TYPE_DIR; #if !defined(_WIN32) else if(S_ISLNK(buf.st_mode)) fst->type = FSL_FSTAT_TYPE_LINK; #endif else /* if(S_ISREG(buf.st_mode)) */{ fst->type = FSL_FSTAT_TYPE_FILE; #if defined(_WIN32) # ifndef S_IXUSR # define S_IXUSR _S_IEXEC # endif if(((S_IXUSR)&buf.st_mode)!=0){ fst->perms |= FSL_FSTAT_PERM_EXE; } #else if( ((S_IXUSR|S_IXGRP|S_IXOTH)&buf.st_mode)!=0 ){ fst->perms |= FSL_FSTAT_PERM_EXE; } #if 0 /* Porting artifact: something to consider... */ else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) ) return PERM_LNK; #endif #endif } } else{ rc = fsl_errno_to_rc(errno, FSL_RC_IO); } return rc; } } fsl_size_t fsl_file_size(const char *zFilename){ fsl_fstat fst; return ( 0 != fsl_stat(zFilename, &fst, 1) ) ? -1 : fst.size; } /* The family of 'wd' functions is historical in nature and not really needed(???) at the library level. 'wd' == 'working directory' (i.e. checkout). Ideally the library won't have to do any _direct_ manipulation of directory trees, e.g. checkouts. That is essentially app-level logic, though we'll need some level of infrastructure for the apps to build off of. When that comes, the "wd" family of functions (or something similar) might come back into play. */ fsl_time_t fsl_file_mtime(const char *zFilename){ fsl_fstat fst; return ( 0 != fsl_stat(zFilename, &fst, 1) ) ? -1 : (fsl_time_t)fst.mtime; } char fsl_is_file(const char *zFilename){ fsl_fstat fst; return ( 0 != fsl_stat(zFilename, &fst, 1) ) ? 0 : (FSL_FSTAT_TYPE_FILE == fst.type) ; } /* ** Return true if zPath is an absolute pathname. Return false ** if it is relative. */ char fsl_is_absolute_path(const char *zPath){ if( zPath && ((zPath[0]=='/') #if defined(_WIN32) || defined(__CYGWIN__) || (zPath[0]=='\\') || (fsl_isalpha(zPath[0]) && zPath[1]==':' && (zPath[2]=='\\' || zPath[2]=='/')) #endif ) ){ return 1; }else{ return 0; } } char fsl_is_simple_pathname(const char *z, char bStrictUtf8){ int i; unsigned char c = (unsigned char) z[0]; char maskNonAscii = bStrictUtf8 ? 0x80 : 0x00; if( c=='/' || c==0 ) return 0; if( c=='.' ){ if( z[1]=='/' || z[1]==0 ) return 0; if( z[1]=='.' && (z[2]=='/' || z[2]==0) ) return 0; } for(i=0; (c=(unsigned char)z[i])!=0; i++){ if( c & maskNonAscii ){ if( (z[++i]&0xc0)!=0x80 ){ /* Invalid first continuation byte */ return 0; } if( c<0xc2 ){ /* Invalid 1-byte UTF-8 sequence, or 2-byte overlong form. */ return 0; }else if( (c&0xe0)==0xe0 ){ /* 3-byte or more */ int unicode; if( c&0x10 ){ /* Unicode characters > U+FFFF are not supported. * Windows XP and earlier cannot handle them. */ return 0; } /* This is a 3-byte UTF-8 character */ unicode = ((c&0x0f)<<12) + ((z[i]&0x3f)<<6) + (z[i+1]&0x3f); if( unicode <= 0x07ff ){ /* overlong form */ return 0; }else if( unicode>=0xe000 ){ /* U+E000..U+FFFF */ if( (unicode<=0xf8ff) || (unicode>=0xfffe) ){ /* U+E000..U+F8FF are for private use. * U+FFFE..U+FFFF are noncharacters. */ return 0; } else if( (unicode>=0xfdd0) && (unicode<=0xfdef) ){ /* U+FDD0..U+FDEF are noncharacters. */ return 0; } }else if( (unicode>=0xd800) && (unicode<=0xdfff) ){ /* U+D800..U+DFFF are for surrogate pairs. */ return 0; } if( (z[++i]&0xc0)!=0x80 ){ /* Invalid second continuation byte */ return 0; } } }else if( bStrictUtf8 && (c=='\\') ){ return 0; } if( c=='/' ){ if( z[i+1]=='/' ) return 0; if( z[i+1]=='.' ){ if( z[i+2]=='/' || z[i+2]==0 ) return 0; if( z[i+2]=='.' && (z[i+3]=='/' || z[i+3]==0) ) return 0; } } } if( z[i-1]=='/' ) return 0; return 1; } /* ** If the last component of the pathname in z[0]..z[j-1] is something ** other than ".." then back it out and return true. If the last ** component is empty or if it is ".." then return false. */ static char fsl_backup_dir(const char *z, fsl_int_t *pJ){ fsl_int_t j = *pJ; fsl_int_t i; if( !j ) return 0; for(i=j-1; i>0 && z[i-1]!='/'; i--){} if( z[i]=='.' && i==j-2 && z[i+1]=='.' ) return 0; *pJ = i-1; return 1; } fsl_size_t fsl_file_simplify_name(char *z, fsl_int_t n_, char slash){ fsl_size_t i; fsl_size_t n = (n_<0) ? fsl_strlen(z) : (fsl_size_t)n_; fsl_int_t j; /* On windows and cygwin convert all \ characters to / */ #if defined(_WIN32) || defined(__CYGWIN__) for(i=0; i1 && z[n-1]=='/' ){ n--; } } /* Remove duplicate '/' characters. Except, two // at the beginning ** of a pathname is allowed since this is important on windows. */ for(i=j=1; i=0 ) z[j] = z[i]; j++; } if( j==0 ) z[j++] = '.'; z[j] = 0; return (fsl_size_t)j; } int fsl_file_canonical_name(const char *zOrigName, fsl_buffer *pOut, char slash){ int rc; if(!zOrigName || !pOut) return FSL_RC_MISUSE; else if( fsl_is_absolute_path(zOrigName) ){ pOut->used = 0; rc = fsl_buffer_append( pOut, zOrigName, -1 ); #if defined(_WIN32) || defined(__CYGWIN__) if(!rc){ char *zOut; /* ** On Windows/cygwin, normalize the drive letter to upper case. */ zOut = fsl_buffer_str(pOut); if( fsl_islower(zOut[0]) && zOut[1]==':' ){ zOut[0] = fsl_toupper(zOut[0]); } } #endif }else{ char zPwd[2000]; rc = fsl_getcwd(zPwd, sizeof(zPwd)-fsl_strlen(zOrigName), NULL); if(!rc){ #if defined(_WIN32) /* ** On Windows, normalize the drive letter to upper case. */ if( !rc && fsl_islower(zPwd[0]) && zPwd[1]==':' ){ zPwd[0] = fsl_toupper(zPwd[0]); } #endif pOut->used = 0; rc = fsl_buffer_appendf(pOut, "%//%/", zPwd, zOrigName); } } if(!rc){ fsl_size_t const newLen = (fsl_size_t)fsl_file_simplify_name(fsl_buffer_str(pOut), (int)pOut->used, slash); rc = fsl_buffer_resize(pOut, newLen); } return rc; } int fsl_file_dirpart(char const * zFilename, fsl_int_t nLen, fsl_buffer * pOut, char leaveSlash){ if(!zFilename || !*zFilename || !pOut) return FSL_RC_MISUSE; else if(!nLen) return FSL_RC_RANGE; else{ fsl_size_t n = (nLen>0) ? (fsl_size_t)nLen : fsl_strlen(zFilename); char const * z = zFilename + n; char doBreak = 0; if(!n) return FSL_RC_RANGE; else while( !doBreak && (--z >= zFilename) ){ switch(*z){ #if defined(_WIN32) case '\\': #endif case '/': if(!leaveSlash) --z; doBreak = 1; break; } } if(z<=zFilename){ return (doBreak && leaveSlash) ? fsl_buffer_append(pOut, zFilename, 1) : fsl_buffer_append(pOut, "", 0) /* ensure a NUL terminator */; }else{ return fsl_buffer_append(pOut, zFilename, z-zFilename + 1); } } } int fsl_find_home_dir( fsl_buffer * tgt, char requireWriteAccess ){ char * zHome = NULL; int rc = 0; tgt->used = 0; #if defined(_WIN32) || defined(__CYGWIN__) zHome = fsl_getenv("LOCALAPPDATA"); if( zHome==0 ){ zHome = fsl_getenv("APPDATA"); if( zHome==0 ){ char *zDrive = fsl_getenv("HOMEDRIVE"); zHome = fsl_getenv("HOMEPATH"); if( zDrive && zHome ){ tgt->used = 0; rc = fsl_buffer_appendf(tgt, "%s", zDrive); fsl_filename_free(zDrive); if(rc){ fsl_filename_free(zHome); return rc; } } } } if(NULL==zHome){ rc = fsl_buffer_append(tgt, "Cannot locate home directory - " "please set the LOCALAPPDATA or " "APPDATA or HOMEPATH " "environment variables.", -1); return rc ? rc : FSL_RC_NOT_FOUND; } rc = fsl_buffer_appendf( tgt, "%/", zHome ); #else /* Unix... */ zHome = fsl_getenv("HOME"); if( zHome==0 ){ rc = fsl_buffer_append(tgt, "Cannot locate home directory - " "please set the HOME environment " "variable.", -1); return rc ? rc : FSL_RC_NOT_FOUND; } rc = fsl_buffer_appendf( tgt, "%s", zHome ); #endif fsl_filename_free(zHome); if(rc) return rc; assert(0used); zHome = fsl_buffer_str(tgt); if( fsl_dir_check(zHome)<1 ){ /* assert(0==tgt->used); */ fsl_buffer tmp = fsl_buffer_empty; rc = fsl_buffer_appendf(&tmp, "Invalid home directory: %s", zHome); fsl_buffer_swap_free(&tmp, tgt, -1); return rc ? rc : FSL_RC_TYPE; } #if !(defined(_WIN32) || defined(__CYGWIN__)) /* Not sure why, but the is-writable check is historically only done on Unix platforms? */ if( requireWriteAccess && (0 != fsl_file_access(zHome, W_OK)) ){ fsl_buffer tmp = fsl_buffer_empty; rc = fsl_buffer_appendf(&tmp, "Home directory [%s] must " "be writeable.", zHome); fsl_buffer_swap_free(&tmp, tgt, -1); return rc ? rc : FSL_RC_ACCESS; } #endif return rc; } int fsl_errno_to_rc(int errNo, int dflt){ switch(errNo){ /* Plese expand on this as tests/use cases call for it... */ case EINVAL: return FSL_RC_MISUSE; case ENOMEM: return FSL_RC_OOM; case EROFS: case EACCES: case EBUSY: case EPERM: /* case EISDIR: this conversion is context-dependent :/ */ return FSL_RC_ACCESS; case ENAMETOOLONG: case ELOOP: return FSL_RC_RANGE; case ENOENT: return FSL_RC_NOT_FOUND; case EEXIST: return FSL_RC_ALREADY_EXISTS; case EIO: return FSL_RC_IO; default: return dflt; } } int fsl_file_unlink(const char *zFilename){ int rc; #ifdef _WIN32 wchar_t *z = (wchar_t*)fsl_utf8_to_filename(zFilename); rc = _wunlink(z); #else char *z = (char *)fsl_utf8_to_filename(zFilename); rc = unlink(zFilename); #endif fsl_filename_free(z); if(rc){ rc = fsl_errno_to_rc(errno, FSL_RC_IO); } return rc; } int fsl_mkdir(const char *zName, char forceFlag){ int rc = /*file_wd_dir_check(zName)*/ fsl_dir_check(zName) ; if( rc<0 ){ if( !forceFlag ) return FSL_RC_TYPE; rc = fsl_file_unlink(zName); if(rc) return rc; } if( 0==rc ){ #if defined(_WIN32) typedef wchar_t char_t; #define mkdir(F,P) _wmkdir(F) #else typedef char char_t; #endif char_t *zMbcs = (char_t*)fsl_utf8_to_filename(zName); if(!zMbcs) return FSL_RC_OOM; rc = mkdir(zMbcs, 0755); fsl_filename_free(zMbcs); if(rc){ rc = fsl_errno_to_rc(errno, FSL_RC_IO); } return rc; #if defined(_WIN32) #undef mkdir #endif } return 0; } int fsl_mkdir2(char const *zName, char forceFlag){ int rc; fsl_buffer b = fsl_buffer_empty /* we copy zName to simplify traversal */; fsl_size_t n = fsl_strlen(zName); fsl_size_t i; char * zCan; if(n<2) return FSL_RC_RANGE; #if 1 /* This variant does more work (checks dirs we know already exist) but transforms the path into something platform-neutral. If we use fsl_file_simplify_name() instead then we end up having to do the trailing-slash logic here. */ rc = fsl_file_canonical_name(zName, &b, 1); if(rc) goto end; #else rc = fsl_buffer_append(&b, zName, n); if(rc) goto end; #endif zCan = fsl_buffer_str(&b); n = b.used; for( i = 1; i < n; ++i ){ if( '/'==zCan[i] ){ zCan[i] = 0; #if defined(_WIN32) || defined(__CYGWIN__) /* ** On Windows, local path looks like: C:/develop/project/file.txt ** The if stops us from trying to create a directory of a drive letter ** C: in this example. */ if( !(i==2 && zCan[1]==':') ){ #endif rc = fsl_dir_check(zCan); #if 0 if(rc<0){ if(forceFlag) rc = fsl_file_unlink(zCan); else rc = FSL_RC_TYPE; if(rc) goto end; } #endif /* MARKER(("dir_check rc=%d, zCan=%s\n", rc, zCan)); */ if(0>=rc){ rc = fsl_mkdir(zCan, forceFlag); /* MARKER(("mkdir(%s) rc=%s\n", zCan, fsl_rc_cstr(rc))); */ if( 0!=rc ) goto end; }else{ rc = 0; /* Nothing to do. */ } #if defined(_WIN32) || defined(__CYGWIN__) } #endif zCan[i] = '/'; } } end: fsl_buffer_clear(&b); return rc; } char fsl_dir_check(const char *zFilename){ fsl_fstat fst; int rc; if( zFilename ){ char *zFN = fsl_mprintf("%s", zFilename); if(!zFN) rc = FSL_RC_OOM; else{ fsl_file_simplify_name(zFN, -1, 0); rc = fsl_stat(zFN, &fst, 1); fsl_free(zFN); } }else{ rc = -1 /*fsl_stat(zFilename, &fst, 1) historic: used static stat cache*/; } return rc ? 0 : ((FSL_FSTAT_TYPE_DIR == fst.type) ? 1 : -1); } #if 0 /* ** Same as dir_check(), but takes into account symlinks. */ int file_wd_dir_check(const char *zFilename){ if(!zFilename || !*zFilename) return FSL_RC_MISUSE; else{ int rc; fsl_fstat fst = fsl_fstat_empty; char *zFN = fsl_strdup(zFilename); if(!zFN) rc = FSL_RC_OOM; else{ fsl_file_simplify_name(zFN, -1, 0); rc = fsl_stat(zFN, &fst, 0); fsl_free(zFN); } return rc ? 0 : ((FSL_FSTAT_TYPE_DIR == fst.type) ? 1 : 2); } } #endif #if 0 /* This block requires permissions flags from v1's manifest.c. */ /* ** Return TRUE if the named file is an executable. Return false ** for directories, devices, fifos, symlinks, etc. */ int fsl_wd_isexe(const char *zFilename){ return fsl_wd_perm(zFilename)==PERM_EXE; } /* ** Return TRUE if the named file is a symlink and symlinks are allowed. ** Return false for all other cases. ** ** On Windows, always return False. */ int file_wd_islink(const char *zFilename){ return file_wd_perm(zFilename)==PERM_LNK; } #endif #if 0 /** ** Same as fsl_is_file(), but takes into account symlinks. */ char fsl_wd_isfile(const char *zFilename); char fsl_wd_isfile(const char *zFilename){ fsl_fstat fst; return ( 0 != fsl_stat(zFilename, &fst, 0) ) ? 0 : (FSL_FSTAT_TYPE_FILE == fst.type); } #endif #if 0 /** ** Same as fsl_file_mtime(), but takes into account symlinks. */ fsl_time_t fsl_wd_mtime(const char *zFilename); fsl_time_t fsl_wd_mtime(const char *zFilename){ fsl_fstat fst; return ( 0 != fsl_stat(zFilename, &fst, 0) ) ? -1 : (fsl_time_t)fst.mtime; } char fsl_wd_isfile_or_link(const char *zFilename){ fsl_fstat fst; return ( 0 != fsl_stat(zFilename, &fst, 0) ) ? 0 : ((FSL_FSTAT_TYPE_LINK == fst.type) || (FSL_FSTAT_TYPE_FILE == fst.type)) ; } #endif #if 0 /** ** Same as fsl_file_size(), but takes into account symlinks. */ fsl_size_t fsl_wd_size(const char *zFilename); fsl_size_t fsl_wd_size(const char *zFilename){ fsl_fstat fst; return ( 0 != fsl_stat(zFilename, &fst, 0) ) ? -1 : fst.size; } #endif void fsl_pathfinder_clear(fsl_pathfinder * pf){ if(pf){ fsl_list_visit_free(&pf->ext, 1); fsl_list_visit_free(&pf->dirs, 1); fsl_buffer_clear(&pf->buf); *pf = fsl_pathfinder_empty; } } static int fsl_pathfinder_add(fsl_list * li, char const * str){ char * cp = fsl_strdup(str); int rc; if(!cp) rc = FSL_RC_OOM; else{ rc = fsl_list_append(li, cp); if(rc) fsl_free(cp); } return rc; } int fsl_pathfinder_dir_add(fsl_pathfinder * pf, char const * dir){ return (pf && dir) ? fsl_pathfinder_add(&pf->dirs, dir) : FSL_RC_MISUSE; } int fsl_pathfinder_ext_add(fsl_pathfinder * pf, char const * ext){ return (pf && ext) ? fsl_pathfinder_add(&pf->ext, ext) : FSL_RC_MISUSE; } int fsl_pathfinder_search(fsl_pathfinder * pf, char const * base, char const ** pOut, fsl_size_t * outLen ){ fsl_buffer * buf = pf ? &pf->buf : NULL; fsl_list * ext; fsl_list * dirs; int rc = 0; fsl_size_t d, x, nD, nX, resetLen = 0; fsl_size_t baseLen; static char const pathSep = #if defined(_WIN32) '\\' #else '/' #endif ; if(!buf || !base || !*base) return FSL_RC_MISUSE; else if(!*base) return FSL_RC_RANGE; else if(0==fsl_file_access( base, 0 )){ if(pOut) *pOut = base; if(outLen) *outLen = fsl_strlen(base); return 0; } baseLen = fsl_strlen(base); ext = &pf->ext; dirs = &pf->dirs; nD = dirs->used; nX = ext->used; for( d = 0; !rc && (d < nD); ++d ){ char const * vD = (char const *)dirs->list[d]; buf->used = 0; if(vD){ fsl_size_t const used = buf->used; rc = fsl_buffer_append(buf, vD, -1); if(rc) return rc; if(used != buf->used){ /* Only append separator if vD is non-empty. */ rc = fsl_buffer_append(buf, &pathSep, 1); if(rc) return rc; } } rc = fsl_buffer_append(buf, base, (fsl_int_t)baseLen); if(rc) return rc; if(0==fsl_file_access( (char const *)buf->mem, 0 )) goto gotone; resetLen = buf->used; for( x = 0; !rc && (x < nX); ++x ){ char const * vX = (char const *)ext->list[x]; if(vX){ buf->used = resetLen; rc = fsl_buffer_append(buf, vX, -1); if(rc) return rc; } assert(buf->used < buf->capacity); buf->mem[buf->used] = 0; if(0==fsl_file_access( (char const *)buf->mem, 0 )){ goto gotone; } } } return FSL_RC_NOT_FOUND; gotone: if(outLen) *outLen = buf->used; if(pOut) *pOut = (char const *)buf->mem; return 0; } #undef MARKER