/* Cockos SWELL (Simple/Small Win32 Emulation Layer for Linux/OSX) Copyright (C) 2006 and later, Cockos, Inc. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This file implements basic win32 GetPrivateProfileString / etc support. It works by caching reads, but writing through on every write that is required (to ensure that updates take, especially when being updated from multiple modules who have their own cache of the .ini file). It is threadsafe, but in theory if two processes are trying to access the same ini, results may go a bit unpredictable (but in general the file should NOT get corrupted, we hope). */ #ifndef SWELL_PROVIDED_BY_APP #include "swell.h" #include "../assocarray.h" #include "../wdlcstring.h" #include "../mutex.h" #include "../queue.h" #include #include #include static void deleteStringKeyedArray(WDL_StringKeyedArray *p) { delete p; } struct iniFileContext { iniFileContext() : m_sections(false,deleteStringKeyedArray) { m_curfn=NULL; m_lastaccesscnt=0; m_curfn_time=0; m_curfn_sz=0; } ~iniFileContext() { } WDL_UINT64 m_lastaccesscnt; time_t m_curfn_time; int m_curfn_sz; char *m_curfn; WDL_StringKeyedArray< WDL_StringKeyedArray * > m_sections; }; #define NUM_OPEN_CONTEXTS 32 static iniFileContext s_ctxs[NUM_OPEN_CONTEXTS]; static WDL_Mutex m_mutex; static time_t getfileupdtimesize(const char *fn, int *szOut) { struct stat st; *szOut = 0; if (!fn || !fn[0] || stat(fn,&st)) return 0; if (S_ISLNK(st.st_mode)) { char *linkpath = realpath(fn,NULL); if (linkpath) { const bool ok = !stat(linkpath,&st); free(linkpath); if (!ok) return 0; } } *szOut = (int)st.st_size; return st.st_mtime; } static bool fgets_to_typedbuf(WDL_TypedBuf *buf, FILE *fp) { int rdpos=0; while (rdpos < 1024*1024*32) { if (buf->GetSize()Resize(rdpos+8192); if (buf->GetSize()Get()+rdpos; *p=0; if (!fgets(p,buf->GetSize()-rdpos,fp) || !*p) break; while (*p) p++; if (p[-1] == '\r' || p[-1] == '\n') break; rdpos = (int) (p - buf->Get()); } return buf->GetSize()>0 && buf->Get()[0]; } // return true on success static iniFileContext *GetFileContext(const char *name) { static WDL_UINT64 acc_cnt; int best_z = 0; char fntemp[512]; if (!name || !strstr(name,"/")) { extern char *g_swell_defini; if (g_swell_defini) { lstrcpyn_safe(fntemp,g_swell_defini,sizeof(fntemp)); } else { const char *p = getenv("HOME"); snprintf(fntemp,sizeof(fntemp),"%s/.libSwell.ini", p && *p ? p : "/tmp"); } if (name && *name) { WDL_remove_fileext(fntemp); snprintf_append(fntemp,sizeof(fntemp),"_%s%s",name, stricmp(WDL_get_fileext(name),".ini")?".ini":""); } name = fntemp; } { int w; WDL_UINT64 bestcnt = 0; bestcnt--; for (w=0;wm_lastaccesscnt=++acc_cnt; int sz=0; if (!ctx->m_curfn || stricmp(ctx->m_curfn,name) || ctx->m_curfn_time != getfileupdtimesize(ctx->m_curfn,&sz) || sz != ctx->m_curfn_sz) { ctx->m_sections.DeleteAll(); // printf("reinitting to %s\n",name); if (!ctx->m_curfn || stricmp(ctx->m_curfn,name)) { free(ctx->m_curfn); ctx->m_curfn=strdup(name); } FILE *fp = WDL_fopenA(name,"r"); if (!fp) { ctx->m_curfn_time=0; ctx->m_curfn_sz=0; return ctx; // allow to proceed (empty file) } flock(fileno(fp),LOCK_SH); // parse .ini file WDL_StringKeyedArray *cursec=NULL; int lcnt=0; for (;;) { static WDL_TypedBuf _buf; if (!fgets_to_typedbuf(&_buf,fp)) break; char *buf = _buf.Get(); if (!ctx->m_sections.GetSize()) { lcnt += strlen(buf); if (lcnt > 256*1024) break; // dont bother reading more than 256kb if no section encountered } char *p=buf; while (*p) p++; if (p>buf) { p--; while (p >= buf && (*p==' ' || *p == '\r' || *p == '\n' || *p == '\t')) p--; p[1]=0; } p=buf; while (*p == ' ' || *p == '\t') p++; if (p[0] == '[') { char *p2=p; while (*p2 && *p2 != ']') p2++; if (*p2) { *p2=0; if (cursec) cursec->Resort(); if (p[1]) { cursec = ctx->m_sections.Get(p+1); if (!cursec) { cursec = new WDL_StringKeyedArray(false,WDL_StringKeyedArray::freecharptr); ctx->m_sections.Insert(p+1,cursec); } else cursec->DeleteAll(); } else cursec=0; } } else if (cursec) { char *t=strstr(p,"="); if (t) { *t++=0; // for maximum win32 compat, we should skip leading whitespace on t, and also trim quotes if any WDL_remove_trailing_whitespace(p); if (*p) cursec->AddUnsorted(p,strdup(t)); } } } ctx->m_curfn_time = getfileupdtimesize(name,&ctx->m_curfn_sz); flock(fileno(fp),LOCK_UN); fclose(fp); if (cursec) cursec->Resort(); } return ctx; } static void WriteBackFile(iniFileContext *ctx) { if (!ctx||!ctx->m_curfn) return; char newfn[1024]; const char *curfn = ctx->m_curfn; struct stat st; char *needfree = NULL; if (!stat(curfn,&st) && S_ISLNK(st.st_mode)) { needfree = realpath(curfn,NULL); if (needfree) curfn = needfree; } lstrcpyn_safe(newfn,curfn,sizeof(newfn)-8); { char *p=newfn; while (*p) p++; while (p>newfn && p[-1] != '/') p--; char lc = '.'; while (*p) { char c = *p; *p++ = lc; lc = c; } *p++ = lc; strcpy(p,".new"); } FILE *fp = WDL_fopenA(newfn,"w"); if (!fp) { free(needfree); return; } flock(fileno(fp),LOCK_EX); int x; for (x = 0; ; x ++) { const char *secname=NULL; WDL_StringKeyedArray * cursec = ctx->m_sections.Enumerate(x,&secname); if (!cursec || !secname) break; fprintf(fp,"[%s]\n",secname); int y; for (y=0;;y++) { const char *keyname = NULL; const char *keyvalue = cursec->Enumerate(y,&keyname); if (!keyvalue || !keyname) break; if (*keyname) fprintf(fp,"%s=%s\n",keyname,keyvalue); } fprintf(fp,"\n"); } fflush(fp); flock(fileno(fp),LOCK_UN); fclose(fp); if (!rename(newfn,curfn)) { ctx->m_curfn_time = getfileupdtimesize(curfn,&ctx->m_curfn_sz); } else { // error updating, hmm how to handle this? } free(needfree); } BOOL WritePrivateProfileSection(const char *appname, const char *strings, const char *fn) { if (!appname) return FALSE; WDL_MutexLock lock(&m_mutex); iniFileContext *ctx = GetFileContext(fn); if (!ctx) return FALSE; WDL_StringKeyedArray * cursec = ctx->m_sections.Get(appname); if (!cursec) { if (!*strings) return TRUE; cursec = new WDL_StringKeyedArray(false,WDL_StringKeyedArray::freecharptr); ctx->m_sections.Insert(appname,cursec); } else cursec->DeleteAll(); if (*strings) { while (*strings) { char buf[8192]; lstrcpyn_safe(buf,strings,sizeof(buf)); char *p = buf; while (*p && *p != '=') p++; if (*p) { *p++=0; cursec->Insert(buf,strdup(strings + (p-buf))); } strings += strlen(strings)+1; } } WriteBackFile(ctx); return TRUE; } BOOL WritePrivateProfileString(const char *appname, const char *keyname, const char *val, const char *fn) { if (!appname || (keyname && !*keyname)) return FALSE; // printf("writing %s %s %s %s\n",appname,keyname,val,fn); WDL_MutexLock lock(&m_mutex); iniFileContext *ctx = GetFileContext(fn); if (!ctx) return FALSE; if (!keyname) { if (ctx->m_sections.Get(appname)) { ctx->m_sections.Delete(appname); WriteBackFile(ctx); } } else { WDL_StringKeyedArray * cursec = ctx->m_sections.Get(appname); if (!val) { if (cursec && cursec->Get(keyname)) { cursec->Delete(keyname); WriteBackFile(ctx); } } else { const char *p; if (!cursec || !(p=cursec->Get(keyname)) || strcmp(p,val)) { if (!cursec) { cursec = new WDL_StringKeyedArray(false,WDL_StringKeyedArray::freecharptr); ctx->m_sections.Insert(appname,cursec); } cursec->Insert(keyname,strdup(val)); WriteBackFile(ctx); } } } return TRUE; } static void lstrcpyn_trimmed(char* dest, const char* src, int len) { if (len<1) return; // Mimic Win32 behavior of stripping quotes and whitespace while (*src==' ' || *src=='\t') ++src; // Strip beginning whitespace const char *end = src; if (*end) while (end[1]) end++; while (end >= src && (*end==' ' || *end=='\t')) --end; // Strip end whitespace if (end > src && ((*src=='\"' && *end=='\"') || (*src=='\'' && *end=='\''))) { // Strip initial set of "" or '' ++src; --end; } int newlen = (int) (end-src+2); if (newlen < 1) newlen = 1; else if (newlen > len) newlen = len; lstrcpyn_safe(dest, src, newlen); } DWORD GetPrivateProfileSection(const char *appname, char *strout, DWORD strout_len, const char *fn) { WDL_MutexLock lock(&m_mutex); if (!strout || strout_len<2) { if (strout && strout_len==1) *strout=0; return 0; } iniFileContext *ctx= GetFileContext(fn); int szOut=0; WDL_StringKeyedArray *cursec = ctx ? ctx->m_sections.Get(appname) : NULL; if (ctx && cursec) { int x; for(x=0;xGetSize();x++) { const char *kv = NULL; const char *val = cursec->Enumerate(x,&kv); if (val && kv) { int l; #define WRSTR(v) \ l = (int)strlen(v); \ if (l > (int)strout_len - szOut - 2) l = (int)strout_len - 2 - szOut; \ if (l>0) { memcpy(strout+szOut,v,l); szOut+=l; } WRSTR(kv) WRSTR("=") #undef WRSTR lstrcpyn_trimmed(strout+szOut, val, (int)strout_len - szOut - 2); szOut += strlen(strout+szOut); l=1; if (l > (int)strout_len - szOut - 1) l = (int)strout_len - 1 - szOut; if (l>0) { memset(strout+szOut,0,l); szOut+=l; } if (szOut >= (int)strout_len-1) { strout[strout_len-1]=0; return strout_len-2; } } } } strout[szOut]=0; if (!szOut) strout[1]=0; return szOut; } DWORD GetPrivateProfileString(const char *appname, const char *keyname, const char *def, char *ret, int retsize, const char *fn) { WDL_MutexLock lock(&m_mutex); // printf("getprivateprofilestring: %s\n",fn); iniFileContext *ctx= GetFileContext(fn); if (ctx) { if (!appname||!keyname) { WDL_Queue tmpbuf; if (!appname) { int x; for (x = 0;; x ++) { const char *secname=NULL; if (!ctx->m_sections.Enumerate(x,&secname) || !secname) break; if (*secname) tmpbuf.Add(secname,(int)strlen(secname)+1); } } else { WDL_StringKeyedArray *cursec = ctx->m_sections.Get(appname); if (cursec) { int y; for (y = 0; ; y ++) { const char *k=NULL; if (!cursec->Enumerate(y,&k)||!k) break; if (*k) tmpbuf.Add(k,(int)strlen(k)+1); } } } int sz=tmpbuf.GetSize()-1; if (sz<0) { ret[0]=ret[1]=0; return 0; } if (sz > retsize-2) sz=retsize-2; memcpy(ret,tmpbuf.Get(),sz); ret[sz]=ret[sz+1]=0; return (DWORD)sz; } WDL_StringKeyedArray *cursec = ctx->m_sections.Get(appname); if (cursec) { const char *val = cursec->Get(keyname); if (val) { lstrcpyn_trimmed(ret,val,retsize); return (DWORD)strlen(ret); } } } // printf("def %s %s %s %s\n",appname,keyname,def,fn); lstrcpyn_safe(ret,def?def:"",retsize); return (DWORD)strlen(ret); } int GetPrivateProfileInt(const char *appname, const char *keyname, int def, const char *fn) { char buf[512]; GetPrivateProfileString(appname,keyname,"",buf,sizeof(buf),fn); if (buf[0]) { int a=atoi(buf); if (a||buf[0]=='0') return a; } return def; } static bool __readbyte(char *src, unsigned char *out) { unsigned char cv=0; int s=4; while(s>=0) { if (*src >= '0' && *src <= '9') cv += (*src-'0')<= 'a' && *src <= 'f') cv += (*src-'a' + 10)<= 'A' && *src <= 'F') cv += (*src-'A' + 10)<0) { if (!__readbyte(src,&cv)) break; *bufout++ = cv; sum += cv; src+=2; } ret = bufsz<0 && __readbyte(src,&cv) && cv==sum; } free(tmp); //printf("getprivateprofilestruct returning %d\n",ret); return ret; } BOOL WritePrivateProfileStruct(const char *appname, const char *keyname, const void *buf, int bufsz, const char *fn) { if (!keyname || !buf) return WritePrivateProfileString(appname,keyname,(const char *)buf,fn); char *tmp=(char *)malloc((bufsz+1)*2+1); if (!tmp) return 0; char *p = tmp; unsigned char sum=0; unsigned char *src=(unsigned char *)buf; while (bufsz-- > 0) { sprintf(p,"%02X",*src); sum+=*src++; p+=2; } sprintf(p,"%02X",sum); BOOL ret=WritePrivateProfileString(appname,keyname,tmp,fn); free(tmp); return ret; } #endif