X11 Work Bench Toolkit  1.0
file_help.c
1 
2 // __ _ _ _ _ //
3 // / _|(_)| | ___ | |__ ___ | | _ __ ___ //
4 // | |_ | || | / _ \ | '_ \ / _ \| || '_ \ / __| //
5 // | _|| || || __/ | | | || __/| || |_) |_| (__ //
6 // |_| |_||_| \___|_____|_| |_| \___||_|| .__/(_)\___| //
7 // |_____| |_| //
8 // //
9 // helper utilities for file I/O //
10 // //
12 
13 /*****************************************************************************
14 
15  X11workbench - X11 programmer's 'work bench' application and toolkit
16  Copyright (c) 2010-2016 by Bob Frazier (aka 'Big Bad Bombastic Bob')
17  all rights reserved
18 
19  DISCLAIMER: The X11workbench application and toolkit software are supplied
20  'as-is', with no warranties, either implied or explicit.
21  Any claims to alleged functionality or features should be
22  considered 'preliminary', and might not function as advertised.
23 
24  BSD-like license:
25 
26  There is no restriction as to what you can do with this software, so long
27  as you include the above copyright notice and DISCLAIMER for any distributed
28  work that is equal to or derived from this one, along with this paragraph
29  that explains the terms of the license if the source is also being made
30  available. A "derived work" describes a work that uses a significant portion
31  of the source files or algorithms that are included with this one.
32  Specifically excluded from this are files that were generated by the software,
33  or anything that is included with the software that is part of another package
34  (such as files that were created or added during the 'configure' process).
35  Specifically included is the use of part or all of any of the X11 workbench
36  toolkit source or header files in your distributed application. If you do not
37  ship the source, the above copyright statement is still required to be placed
38  in a reasonably prominent place, such as documentation, splash screens, and/or
39  'about the application' dialog boxes.
40 
41  Use and distribution are in accordance with GPL, LGPL, and/or the above
42  BSD-like license. See COPYING and README files for more information.
43 
44 
45  Additional information at http://sourceforge.net/projects/X11workbench
46 
47 ******************************************************************************/
48 
49 
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <unistd.h>
53 #include <memory.h>
54 #include <string.h>
55 #include <strings.h>
56 #include <fcntl.h>
57 #include <dirent.h>
58 #include <fnmatch.h>
59 #include <errno.h>
60 #include <sys/types.h>
61 #include <sys/stat.h>
62 #include <sys/param.h> // for MAXPATHLEN and PATH_MAX (also includes limits.h in some cases)
63 
64 #include "file_help.h"
65 #include "draw_text.h" // for string utilities, mostly
66 #include "window_helper.h" // for debug stuff
67 
68 
69 #ifndef NO_DEBUG
70 // debug function to dump parsed contents of file_help_buf_t
71 static void FBDumpParsedFileBuf(file_help_buf_t *pBuf)
72 {
73 int i1;
74 const char *p1;
75 
77  {
78  WBDebugPrint("dumping file buf - %d lines\n", (int)pBuf->lLineCount);
79 
80  for(i1=0; i1 < (int)pBuf->lLineCount; i1++)
81  {
82  p1 = pBuf->ppLineBuf[i1 + 1];
83  if(!p1)
84  {
85  p1 = pBuf->ppLineBuf[i1] + strlen(pBuf->ppLineBuf[i1]);
86  }
87 
88  while(p1 > pBuf->ppLineBuf[i1] && (*(p1 - 1) == '\n' || *(p1 - 1) == '\r'))
89  {
90  p1--;
91  }
92 
93  if(p1 > pBuf->ppLineBuf[i1])
94  {
95  WBDebugPrint("%3d: \"%.*s\"..\n", i1, (int)(p1 - pBuf->ppLineBuf[i1]), pBuf->ppLineBuf[i1]);
96  }
97  else
98  {
99  WBDebugPrint("%3d: [empty]\n", i1);
100  }
101  }
102  }
103 }
104 #endif // NO_DEBUG
105 
106 file_help_buf_t *FBGetFileBuf(const char *szFileName)
107 {
108  int iFile;
109  file_help_buf_t *pRval;
110 
111  iFile = open(szFileName, O_RDONLY);
112  if(iFile < 0)
113  {
114  return NULL;
115  }
116 
117  pRval = FBGetFileBufViaHandle(iFile);
118  close(iFile);
119 
120  return pRval;
121 }
122 
124 {
125  file_help_buf_t *pRval;
126  long lFileSize;
127 
128  lFileSize = (long)lseek(iFile, 0, SEEK_END);
129  lseek(iFile, 0, SEEK_SET);
130 
131  if(lFileSize < 0)
132  {
133  return NULL;
134  }
135 
136  pRval = (file_help_buf_t *)WBAlloc(sizeof(*pRval) + ((lFileSize + 256 + 128) & 0xffffff00L));
137 
138  if(pRval)
139  {
140  bzero(pRval, sizeof(*pRval)); // this also takes care of pNext, pPrev
141 
142  pRval->lBufferSize = sizeof(*pRval) + ((lFileSize + 256 + 128) & 0xffffff00L);
143  pRval->lBufferCount = lFileSize;
144  pRval->iFlags = 0;
145 
146  if(lFileSize)
147  {
148  if(read(iFile, pRval->cData, lFileSize) != lFileSize)
149  {
150  WBFree(pRval);
151  pRval = NULL;
152  }
153  }
154  }
155 
156  return pRval;
157 }
158 
159 file_help_buf_t *FBGetFileBufFromBuffer(const char *pBuf, long cbBuf)
160 {
161  file_help_buf_t *pRval;
162 
163  if(cbBuf < 0)
164  {
165  return NULL;
166  }
167 
168  pRval = (file_help_buf_t *)WBAlloc(sizeof(*pRval) + ((cbBuf + 256 + 128) & 0xffffff00L));
169  if(pRval)
170  {
171  bzero(pRval, sizeof(*pRval)); // this also takes care of pNext, pPrev
172 
173  pRval->lBufferSize = sizeof(*pRval) + ((cbBuf + 256 + 128) & 0xffffff00L);
174  pRval->lBufferCount = cbBuf;
175  pRval->iFlags = 0;
176 
177  if(cbBuf && pBuf)
178  {
179  memcpy(pRval->cData, pBuf, cbBuf);
180  }
181  }
182 
183  return pRval;
184 }
185 
187 {
188  if(!pBuf)
189  {
190  return;
191  }
192 
193  if(pBuf->pNext)
194  {
195  FBDestroyFileBuf(pBuf->pNext); // recursively destroy (TODO: loop instead?)
196  pBuf->pNext = NULL;
197  }
198 
199  // TODO: ppLineBuf should be part of 'cData'. For now it's WBAlloc'd
200 
201  if(pBuf->ppLineBuf)
202  {
203  WBFree(pBuf->ppLineBuf);
204  }
205 
206  WBFree(pBuf);
207 }
208 
210 {
211  int i1, iLines;
212  const char *p1, /* *p2,*/ *pEnd;
213 
214  // TODO: ppLineBuf should be part of 'cData'. For now it's WBAlloc'd
215 
216  if(pBuf->ppLineBuf)
217  {
218  WBFree(pBuf->ppLineBuf);
219  pBuf->ppLineBuf = NULL;
220  pBuf->lLineCount = 0;
221  pBuf->lLineBufSize = 0;
222  }
223 
224  // count the lines first
225  for(i1=0, p1 = pBuf->cData, pEnd = pBuf->cData + pBuf->lBufferCount; p1 < pEnd; )
226  {
227 // NOTE: p2 not being used; commented out because of linux gcc warnings
228 // p2 = p1;
229  while(p1 < pEnd && *p1 != '\n')
230  {
231  p1++;
232  }
233  if(p1 < pEnd)
234  {
235  p1++; // skipping past the newline
236  }
237 
238  i1++; // this counts the last line regardless of the '\n' being there or not
239  // and works regardless of compiler behavior outside of a 'for' loop
240  }
241 
242  // now allocate memory and REALLY assign the pointers
243  iLines = i1;
244 
245  // TODO: ppLineBuf should be part of 'cData'. For now it's WBAlloc'd
246  // if I need room for more lines, attach another file_help_buf_t
247  // and chain it to 'pNext'
248 
249  pBuf->ppLineBuf = (char **)WBAlloc((iLines + 1) * sizeof(char **));
250  if(!pBuf->ppLineBuf)
251  {
252  return -1;
253  }
254 
255  pBuf->lLineBufSize = (iLines + 1) * sizeof(char **);
256  pBuf->lLineCount = iLines;
257 
258  for(i1=0, p1 = pBuf->cData, pEnd = pBuf->cData + pBuf->lBufferCount; p1 < pEnd && i1 < iLines; i1++)
259  {
260  pBuf->ppLineBuf[i1] = (char *)p1;
261 
262  while(p1 < pEnd && *p1 != '\n')
263  {
264  p1++;
265  }
266  if(p1 < pEnd)
267  {
268  p1++; // skipping past the newline
269  }
270  }
271 
272  pBuf->ppLineBuf[iLines] = NULL; // always (to mark "the end")
273 
274 #ifndef NO_DEBUG
275  FBDumpParsedFileBuf(pBuf);
276 #endif // NO_DEBUG
277 
278  return 0;
279 }
280 
281 int FBWriteFileBuf(const char *szFileName, const file_help_buf_t *pBuf)
282 {
283  int iFile, iRval;
284 
285  if(!pBuf)
286  {
287  return -1;
288  }
289 
290  iFile = open(szFileName, O_CREAT | O_TRUNC | O_RDWR, 0666); // always create with mode '666' (umask should apply)
291 
292  if(iFile < 0)
293  {
294  return -1;
295  }
296 
297  iRval = FBWriteFileBufHandle(iFile, pBuf);
298  close(iFile);
299 
300  return(iRval);
301 }
302 
303 int FBWriteFileBufHandle(int iFile, const file_help_buf_t *pBuf)
304 {
305  if(!pBuf)
306  {
307  return -1;
308  }
309 
310  lseek(iFile, 0, SEEK_SET); // rewind
311 
312  if(pBuf->lBufferCount > 0)
313  {
314  if(write(iFile, pBuf->cData, pBuf->lBufferCount)
315  != pBuf->lBufferCount)
316  {
317  return -2;
318  }
319  }
320 
321  ftruncate(iFile, pBuf->lBufferCount); // ensure file size is correct
322 
323  ((file_help_buf_t *)pBuf)->iFlags &= ~file_help_buf_dirty;
324  return pBuf->lBufferCount;
325 }
326 
327 static int SanityCheckFileBuf(file_help_buf_t *pBuf, const char *szFunction)
328 {
329  if(!pBuf || pBuf->lBufferSize < sizeof(*pBuf)
330  || pBuf->lBufferCount > pBuf->lBufferSize + sizeof(pBuf->cData) - sizeof(*pBuf))
331  {
332  WB_ERROR_PRINT("%s - error in file buf - %p %ld %ld (%ld)\n",
333  szFunction,
334  pBuf,
335  pBuf ? pBuf->lBufferSize : 0L,
336  pBuf ? pBuf->lBufferCount : 0L,
337  pBuf ? pBuf->lBufferSize + sizeof(pBuf->cData) - sizeof(*pBuf) : 0L);
338  return -1;
339  }
340 
341  return 0;
342 }
343 
344 static int InternalGrowFileBuf(file_help_buf_t **ppBuf, long cbOffset, long cbData)
345 { // return value is negative on error
346  long lNewSize, lActualBufSize;
347  void *pNew;
348 
349  if(!ppBuf)
350  {
351  WB_ERROR_PRINT("NULL 'ppBuf' in InternalGrowFileBuf\n");
352  return -1;
353  }
354 
355  if(SanityCheckFileBuf(*ppBuf, __FUNCTION__) < 0)
356  {
357  return -1;
358  }
359 
360  lActualBufSize = (*ppBuf)->lBufferSize - sizeof(**ppBuf); // exclude 'cData' for the moment
361 
362  if(cbOffset > (*ppBuf)->lBufferCount)
363  {
364  lNewSize = cbOffset + cbData;
365  }
366  else
367  {
368  lNewSize = (*ppBuf)->lBufferCount + cbData;
369  }
370 
371  if(lActualBufSize >= lNewSize)
372  {
373  return 0; // no re-allocation
374  }
375 
376  lNewSize = (lNewSize + sizeof(*ppBuf) + 256 + 128) & 0xffffff00L;
377 
378  WB_DEBUG_PRINT(DebugLevel_Excessive, "TEMPORARY: reallocating - %d %d %d\n",
379  (int)(*ppBuf)->lBufferSize, (int)lActualBufSize, (int)lNewSize);
380 
381  pNew = (char *)WBReAlloc(*ppBuf, lNewSize);
382 
383  if(pNew)
384  {
385  *ppBuf = (file_help_buf_t *)pNew;
386 
387  (*ppBuf)->lBufferSize = lNewSize;
388 
389  // it's been my experience that the additional allocated memory may be uninitialized,
390  // so zero it out, starting with the end of valid data.
391 
392  lActualBufSize = lNewSize - sizeof(**ppBuf) + sizeof((*ppBuf)->cData);
393 
394  if((*ppBuf)->lBufferCount < lActualBufSize) // just in case, test for this
395  {
396  memset((char *)((*ppBuf)->cData) + (*ppBuf)->lBufferCount, 0, lActualBufSize - (*ppBuf)->lBufferCount);
397  }
398 
399  return lNewSize; // returns the NEW size
400  }
401 
402  WB_ERROR_PRINT("error re-allocating file buf\n");
403  return -2; // error re-allocating
404 }
405 
406 void FBInsertIntoFileBuf(file_help_buf_t **ppBuf, long cbOffset, const void *pData, long cbData)
407 {
408  // insert 'cbData' bytes of 'pData' at 'cbOffset' within *ppBuf, possibly re-allocating the
409  // buffer and re-assigning it to *ppBuf.
410 
411  if(InternalGrowFileBuf(ppBuf, cbOffset, cbData) < 0)
412  {
413  return;
414  }
415 
416  if((*ppBuf)->lBufferCount < cbOffset) // insert PAST THE END of the buffer
417  {
418  memset((char *)((*ppBuf)->cData) + (*ppBuf)->lBufferCount, '\n', cbOffset - (*ppBuf)->lBufferCount); // pad with newlines
419  (*ppBuf)->lBufferCount = cbOffset; // since I effectively increased the data count to THIS
420  }
421  else if((*ppBuf)->lBufferCount > cbOffset) // insert into the middle of the buffer
422  {
423  memmove((char *)((*ppBuf)->cData) + cbOffset + cbData, (char *)((*ppBuf)->cData) + cbOffset, (*ppBuf)->lBufferCount - cbOffset);
424  }
425 
426  memcpy((char *)((*ppBuf)->cData) + cbOffset, pData, cbData);
427  (*ppBuf)->lBufferCount += cbData; // new buffer data count
428 
429  SanityCheckFileBuf(*ppBuf, __FUNCTION__);
430 
431  (*ppBuf)->iFlags |= file_help_buf_dirty;
432 }
433 
434 void FBDeleteFromFileBuf(file_help_buf_t *pBuf, long cbOffset, long cbDelFrom)
435 {
436  // remove 'cbDelFrom' bytes of data from 'pBuf' starting at offset 'cbOffset'
437 
438  if(cbOffset >= pBuf->lBufferCount || cbDelFrom <= 0)
439  {
440  return;
441  }
442 
443  if(cbOffset + cbDelFrom > pBuf->lBufferCount)
444  {
445  cbDelFrom = pBuf->lBufferCount - cbOffset;
446  }
447  else if(cbOffset + cbDelFrom < pBuf->lBufferCount)
448  {
449  memcpy(pBuf->cData + cbOffset, pBuf->cData + cbOffset + cbDelFrom,
450  pBuf->lBufferCount - cbOffset - cbDelFrom);
451  }
452 
453  pBuf->lBufferCount -= cbDelFrom;
454  memset(pBuf->cData + pBuf->lBufferCount, 0, cbDelFrom); // zero out what WAS there
455 
456  SanityCheckFileBuf(pBuf, __FUNCTION__);
457 
458  pBuf->iFlags |= file_help_buf_dirty;
459 }
460 
461 void FBInsertLineIntoFileBuf(file_help_buf_t **ppBuf, long lLineNum, const char *szLine)
462 {
463  int i1, i2;
464  long lOffset;
465  char *pDest;
466 
467  // if 'lLineNum' exceeds the current line count, add lines until it matches
468 
469  i2 = strlen(szLine);
470  while(i2 && (szLine[i2 - 1] == '\n' || szLine[i2 - 1] == '\r')) // trim <return> <newline> [will add later]
471  {
472  i2--; // I'll always need to have a newline, so don't include the newline if it already has one
473  }
474 
475  if(!(*ppBuf)->ppLineBuf) // make sure it has been parsed, FIRST
476  {
477  FBParseFileBuf(*ppBuf);
478  if(!(*ppBuf)->ppLineBuf)
479  {
480  WB_ERROR_PRINT("%s - no line buffer!\n", __FUNCTION__);
481  return; // can't (some kind of error)
482  }
483  }
484 
485  i1 = lLineNum - (*ppBuf)->lLineCount; // lLineNum is zero-based
486  if(i1 < 0) // line number is valid
487  {
488  i1 = 0;
489 
490  pDest = (*ppBuf)->ppLineBuf[lLineNum];
491  if(!pDest)
492  {
493  pDest = (*ppBuf)->cData + (*ppBuf)->lBufferCount;
494  }
495  }
496  else
497  {
498  pDest = (*ppBuf)->cData + (*ppBuf)->lBufferCount + i1; // TODO: how do I properly deal with 'in between' lines?
499  }
500 
501  lOffset = (char *)pDest - (char *)((*ppBuf)->cData); // byte offset of the insertion point
502 
503  FBInsertIntoFileBuf(ppBuf, lOffset, szLine, i2); // make room for 2 extra chars, hence 'i2 + 2'
504  FBInsertIntoFileBuf(ppBuf, lOffset + i2, "\r\n", 2); // the trailing CRLF
505 
506  if(i1 > 0)
507  {
508  // fix added newlines, and make sure that 'szLine' ends with a newline as well
509 
510  WB_DEBUG_PRINT(DebugLevel_Chatty, "%s inserting %d blank lines\n", __FUNCTION__, i1);
511  pDest = (*ppBuf)->cData + lOffset; // re-assign since 'ppBuf' may have changed
512 
513  while(i1 > 0) // inserting newline chars for 'in between' blank lines
514  {
515  *(pDest - i1) = '\n';
516  i1--;
517  }
518  }
519 
520  // re-parse file
521 
522  FBParseFileBuf(*ppBuf); // re-parse at the end
523 }
524 
525 void FBDeleteLineFromFileBuf(file_help_buf_t *pBuf, long lLineNum)
526 {
527 // int i1, i2;
528  long lOffset, lEndOffset;
529 // char *pDest;
530 
531  if(!pBuf->ppLineBuf)
532  {
533  FBParseFileBuf(pBuf);
534  }
535 
536  // if 'lLineNum' exceeds the current line count, just return
537 
538  if(!pBuf->ppLineBuf || lLineNum > pBuf->lLineCount)
539  {
540  return;
541  }
542  if(lLineNum == pBuf->lLineCount)
543  {
544  lEndOffset = pBuf->lBufferCount;
545  }
546  else
547  {
548  lEndOffset = (char *)pBuf->ppLineBuf[lLineNum + 1] - (char *)(pBuf->cData);
549  }
550 
551  lOffset = (char *)pBuf->ppLineBuf[lLineNum] - (char *)(pBuf->cData);
552 
553  if(lOffset < lEndOffset)
554  {
555  FBDeleteFromFileBuf(pBuf, lOffset, lEndOffset - lOffset);
556  }
557 
558  FBParseFileBuf(pBuf);
559 }
560 
561 void FBReplaceLineInFileBuf(file_help_buf_t **ppBuf, long lLineNum, const char *szLine)
562 {
563  // TODO: make this more efficient
564  FBDeleteLineFromFileBuf(*ppBuf, lLineNum);
565  FBInsertLineIntoFileBuf(ppBuf, lLineNum, szLine);
566 }
567 
568 
569 // FILE SYSTEM INDEPENDENT FILE AND DIRECTORY UTILITIES
570 // UNIX/LINUX versions - TODO windows versions?
571 
572 int WBReplicateFilePermissions(const char *szProto, const char *szTarget)
573 {
574 struct stat sb;
575 int iRval = 0;
576 
577  iRval = stat(szProto, &sb); // TODO: lstat for symlink?
578  if(!iRval)
579  {
580  // TODO: chflags?
581  // TODO: what if it's a symlink?
582  iRval = chmod(szTarget, sb.st_mode & 0777); // only set the rwx permissions, and ignore others
583  if(!iRval)
584  {
585  if(geteuid() == 0 || getuid() == sb.st_uid) // only do this if owner matches or I'm root
586  {
587  iRval = chown(szTarget, sb.st_uid, sb.st_gid);
588  if(iRval < 0 && geteuid() != 0)
589  {
590  iRval = chown(szTarget, -1, sb.st_gid); // don't change the user
591 
592  if(iRval < 0)
593  {
594  // don't bother changing anything - just warn??
595  iRval = 0; // for now...
596  }
597  }
598  }
599  }
600  }
601 
602  return iRval;
603 }
604 
606 {
607 char *pRval = WBAlloc(MAXPATHLEN + 2);
608 int i1;
609 
610  if(pRval)
611  {
612  if(!getcwd(pRval, MAXPATHLEN))
613  {
614  WBFree(pRval);
615  pRval = NULL;
616  }
617  }
618 
619  // this function will always return something that ends in '/' (except on error)
620 
621  if(pRval)
622  {
623  i1 = strlen(pRval);
624 
625  if(i1 > 0 && pRval[i1 - 1] != '/')
626  {
627  pRval[i1] = '/';
628  pRval[i1 + 1] = 0;
629  }
630  }
631 
632  return pRval;
633 }
634 
635 int WBIsDirectory(const char *szFileName)
636 {
637 int bRval = 0;
638 
639 #ifdef WIN32
640 WIN32_FIND_DATA fd;
641 HANDLE hFF;
642 
643  hFF = FindFirstFile(szFileName, &fd);
644 
645  if(hFF != INVALID_HANDLE_VALUE)
646  {
647  bRval = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
648  FindClose(hFF);
649  }
650 
651 #else // WIN32
652 struct stat sF;
653 
654  if(!stat(szFileName, &sF)) // NOTE: 'stat' returns info about symlink targets, not the link itself
655  bRval = S_ISDIR(sF.st_mode);
656 
657 #endif // WIN32
658 
659  return(bRval);
660 }
661 
662 char *WBGetCanonicalPath(const char *szFileName)
663 {
664 char *pTemp, *p1, *p2, *p3, *p4, *pRval = NULL;
665 struct stat sF;
666 
667  pTemp = WBCopyString(szFileName);
668 
669  if(!pTemp)
670  {
671  return NULL;
672  }
673 
674  // step 1: eliminate // /./
675 
676  p1 = pTemp;
677  while(*p1 && p1[1])
678  {
679  if(*p1 == '/' && p1[1] == '/')
680  {
681  memmove(p1, p1 + 1, strlen(p1 + 1) + 1);
682  }
683  else if(*p1 == '/' && p1[1] == '.' && p1[2] == '/')
684  {
685  memmove(p1, p1 + 2, strlen(p1 + 2) + 1);
686  }
687  else
688  {
689  p1++;
690  }
691  }
692 
693  // step 2: resolve each portion of the path, deal with '~' '.' '..', build new path.
694 
695  if(*pTemp == '~' && (pTemp[1] == '/' || !pTemp[1])) // first look for '~' at the beginning (only allowed there)
696  {
697  p1 = getenv("HOME");
698  if(!p1 || !*p1) // no home directory?
699  {
700  *pTemp = '.'; // for now change it to '.'
701  }
702  else
703  {
704  p3 = WBCopyString(p1);
705  if(!p3)
706  {
707  WBFree(pTemp);
708  return NULL;
709  }
710 
711  if(p3[strlen(p3) - 1] != '/')
712  {
713  WBCatString(&p3, "/");
714  }
715 
716  p2 = pTemp + 1;
717  if(*p2 == '/')
718  {
719  p2++; // already have an ending / on the path
720  }
721 
722  if(*p2)
723  {
724  WBCatString(&p3, p2);
725  }
726 
727  WBFree(pTemp);
728  pTemp = p3;
729  }
730  }
731 
732  p1 = pTemp;
733  while(*p1)
734  {
735  p2 = strchr(p1, '/');
736  if(!p2)
737  {
738  if(*p1 == '.') // check for ending in '.' or '..' and add a '/' so I can handle it correctly
739  {
740  if((p1[1] == '.' && !p1[2]) || !p1[1])
741  {
742  p2 = pTemp; // temporary
743  WBCatString(&pTemp, "/");
744 
745  p1 = (p1 - p2) + pTemp; // restore relative pointer
746 
747  WB_ERROR_PRINT("TEMPORARY: %s %s\n", p1, pTemp);
748 
749  continue; // let's do this again, properly
750  }
751  }
752 
753  // no more paths, so this is "the name".
754  if(!pRval) // no existing path, use CWD
755  {
756  pRval = WBGetCurrentDirectory();
757 
758  if(!pRval)
759  {
760  break;
761  }
762  }
763 
764  WBCatString(&pRval, p1);
765 
766  break;
767  }
768  else if(p2 == p1)
769  {
770  pRval = WBCopyString("/");
771  }
772  else
773  {
774  if(!pRval)
775  {
776  pRval = WBGetCurrentDirectory();
777  if(!pRval)
778  {
779  break;
780  }
781  }
782 
783  // when I assemble these paths together, deal with '..' and
784  // symbolic links. Check for cyclic paths.
785 
786  if(p2 - p1 == 1 && p1[0] == '.') // the ./ path
787  {
788  p1 = p2 + 1; // just ignore this part
789  continue;
790  }
791  else if(p2 - p1 == 2 && p1[0] == '.' && p1[1] == '.') // the ../ path
792  {
793  p1 = p2 + 1; // I need to fix the path while ignoring the '../' part
794 
795  p3 = pRval + strlen(pRval) - 1; // NOTE: pRval ends in '/' and I want the one BEFORE that
796  while(p3 > pRval)
797  {
798  if(*(p3 - 1) == '/')
799  {
800  *p3 = 0;
801  break;
802  }
803 
804  p3--;
805  }
806 
807  if(p3 <= pRval) // did not find a preceding '/' - this is an error
808  {
809  WB_ERROR_PRINT("%s:%d - did not find preceding '/' - %s\n", __FUNCTION__, __LINE__, pRval);
810 
811  WBFree(pRval);
812  pRval = NULL;
813 
814  break;
815  }
816 
817  continue;
818  }
819 
820  // TEMPORARY: just copy as-is to test basic logic
821 
822  WBCatStringN(&pRval, p1, p2 - p1 + 1); // include the '/' at the end
823  if(!pRval)
824  {
825  WB_ERROR_PRINT("%s:%d - WBCatStringN returned NULL pointer\n", __FUNCTION__, __LINE__);
826 
827  break;
828  }
829 
830  // see if this is a symbolic link. exclude testing '/'
831 
832  p3 = pRval + strlen(pRval) - 1;
833  if(p3 > pRval)
834  {
835  *p3 = 0; // temporary
836  if(lstat(pRval, &sF)) // get the file 'stat' and see if we're a symlink
837  {
838  // error, does not exist? - leave it 'as-is' for now
839  *p3 = '/'; // restore it
840  }
841  else if(S_ISDIR(sF.st_mode)) // an actual directory - remains as-is
842  {
843  // don't do anything except restore the '/'
844  *p3 = '/'; // restore it
845  }
846  else if(S_ISLNK(sF.st_mode)) // symlink
847  {
848  // now I get to put the symlink contents "in place". If the symlink is
849  // relative to the current directory, I'll want that.
850 
851  p4 = (char *)WBAlloc(MAXPATHLEN + 2);
852 
853  if(!p4)
854  {
855  WB_ERROR_PRINT("%s:%d - not enough memory for buffer\n", __FUNCTION__, __LINE__);
856 
857  WBFree(pRval);
858  pRval = NULL;
859  break;
860  }
861  else
862  {
863  int iLen = readlink(pRval, p4, MAXPATHLEN);
864 
865  if(iLen <= 0)
866  {
867  WB_ERROR_PRINT("%s:%d - readlink returned %d for %s\n", __FUNCTION__, __LINE__, iLen, pRval);
868 
869  WBFree(p4);
870  WBFree(pRval);
871  pRval = NULL;
872 
873  break;
874  }
875 
876  p4[iLen] = 0; // assume < MAXPATHLEN for now...
877  if(p4[0] == '/') // it's an absolute path
878  {
879  WBFree(pRval);
880  pRval = p4;
881  }
882  else
883  {
884  while(p3 > pRval && *(p3 - 1) != '/') // scan back for a '/'
885  {
886  p3--;
887  }
888 
889  *p3 = 0;
890  WBCatString(&pRval, p4); // sub in the relative path
891  WBFree(p4);
892  }
893 
894  if(!WBIsDirectory(pRval)) // must be a directory!
895  {
896  WB_ERROR_PRINT("%s:%d - %s not a directory\n", __FUNCTION__, __LINE__, pRval);
897 
898  WBFree(pRval);
899  pRval = NULL;
900  break; // this is an error
901  }
902  else
903  {
904  WBCatString(&pRval, "/");
905 
906  if(pRval)
907  {
908  p4 = WBGetCanonicalPath(pRval); // recurse
909 
910  WBFree(pRval);
911  pRval = p4; // new canonical version of symlink path
912  }
913 
914  if(!pRval)
915  {
916  WB_ERROR_PRINT("%s:%d - NULL pRval\n", __FUNCTION__, __LINE__);
917 
918  break;
919  }
920  }
921  }
922  }
923  }
924  }
925 
926  p1 = p2 + 1;
927  }
928 
929  // if the resulting path is a symbolic link, fix it
930  if(pRval)
931  {
932  p1 = pRval + strlen(pRval) - 1;
933 
934  if(p1 > pRval && *p1 != '/') // does not end in a slash, so it should be a file...
935  {
936  while(p1 > pRval && *(p1 - 1) != '/')
937  {
938  p1--;
939  }
940 
941  if(!lstat(pRval, &sF)) // get the file 'stat' and see if we're a symlink (ignore errors)
942  {
943  if(S_ISDIR(sF.st_mode)) // an actual directory - end with a '/'
944  {
945  WBCatString(&pRval, "/"); // add ending '/'
946  }
947  else if(S_ISLNK(sF.st_mode)) // symlink
948  {
949  // now I get to put the symlink contents "in place". If the symlink is
950  // relative to the current directory, I'll want that.
951 
952  p4 = (char *)WBAlloc(MAXPATHLEN + 2);
953 
954  if(!p4)
955  {
956  WB_ERROR_PRINT("%s:%d - not enough memory\n", __FUNCTION__, __LINE__);
957 
958  // TODO: assign pRval to NULL ?
959  }
960  else
961  {
962  int iLen = readlink(pRval, p4, MAXPATHLEN);
963 
964  if(iLen <= 0)
965  {
966  WB_ERROR_PRINT("%s:%d - readlink returned %d for %s\n", __FUNCTION__, __LINE__, iLen, pRval);
967 
968  WBFree(p4);
969  WBFree(pRval);
970  pRval = NULL;
971  }
972  else
973  {
974  p4[iLen] = 0; // assume < MAXPATHLEN for now...
975  if(p4[0] == '/') // it's an absolute path
976  {
977  WBFree(pRval); // new path for old
978  pRval = p4;
979  }
980  else
981  {
982  p3 = pRval + strlen(pRval); // I won't be ending in '/' for this part so don't subtract 1
983  while(p3 > pRval && *(p3 - 1) != '/') // scan back for the '/' in symlink's original path
984  {
985  p3--;
986  }
987 
988  *p3 = 0;
989  WBCatString(&pRval, p4); // sub in the relative path
990  WBFree(p4);
991  }
992 
993  if(pRval && WBIsDirectory(pRval)) // is the result a directory?
994  {
995  WBCatString(&pRval, "/");
996  }
997 
998  if(pRval)
999  {
1000  p4 = WBGetCanonicalPath(pRval); // recurse to make sure I'm canonical (deal with '..' and '.' and so on)
1001 
1002  WBFree(pRval);
1003  pRval = p4; // new canonical version of symlink path
1004  }
1005  }
1006  }
1007  }
1008  }
1009  }
1010  }
1011 
1012  if(pTemp)
1013  {
1014  WBFree(pTemp);
1015  pTemp = NULL; // by convention
1016  }
1017 
1018  if(!pRval)
1019  {
1020  WB_ERROR_PRINT("%s:%d - returning NULL\n", __FUNCTION__, __LINE__);
1021  }
1022 
1023  return pRval;
1024 }
1025 
1026 // reading directories in a system-independent way
1027 
1028 typedef struct __DIRLIST__
1029 {
1030  const char *szPath, *szNameSpec;
1031 #ifdef WIN32 // also true for WIN64
1032  WIN32_FIND_DATA fd;
1033  HANDLE hFF;
1034 #else // !WIN32
1035  DIR *hD;
1036  struct stat sF;
1037  union
1038  {
1039  char cde[sizeof(struct dirent) + NAME_MAX + 2];
1040  struct dirent de;
1041  };
1042 #endif // WIN32 or !WIN32 - that is the question
1043 // actual composite 'search name' follows
1044 } DIRLIST;
1045 
1046 void *WBAllocDirectoryList(const char *szDirSpec)
1047 {
1048 DIRLIST *pRval;
1049 char *p1, *p2;
1050 int iLen, nMaxLen;
1051 char *pBuf;
1052 
1053  if(!szDirSpec || !*szDirSpec)
1054  {
1055  WB_WARN_PRINT("WARNING - %s - invalid directory (NULL or empty)\n", __FUNCTION__);
1056  return NULL;
1057  }
1058 
1059  iLen = strlen(szDirSpec);
1060  nMaxLen = iLen + 32;
1061 
1062  pBuf = WBAlloc(nMaxLen);
1063  if(!pBuf)
1064  {
1065  WB_ERROR_PRINT("ERROR - %s - Unable to allocate memory for buffer size %d\n", __FUNCTION__, nMaxLen);
1066  return NULL;
1067  }
1068 
1069  if(szDirSpec[0] == '/') // path starts from the root
1070  {
1071  memcpy(pBuf, szDirSpec, iLen + 1);
1072  }
1073  else // for now, force a path of './' to be prepended to path spec
1074  {
1075  pBuf[0] = '.';
1076  pBuf[1] = '/';
1077 
1078  memcpy(pBuf + 2, szDirSpec, iLen + 1);
1079  iLen += 2;
1080  }
1081 
1082  // do a reverse scan until I find a '/'
1083  p1 = ((char *)pBuf) + iLen;
1084  while(p1 > pBuf && *(p1 - 1) != '/')
1085  {
1086  p1--;
1087  }
1088 
1089 // WB_ERROR_PRINT("TEMPORARY - \"%s\" \"%s\" \"%s\"\n", pBuf, p1, szDirSpec);
1090 
1091  if(p1 > pBuf)
1092  {
1093  // found, and p1 points PAST the '/'. See if it ends in '/' or if there are wildcards present
1094  if(!*p1) // name ends in '/'
1095  {
1096  if(p1 == (pBuf + 1) && *pBuf == '/') // root dir
1097  {
1098  p1++;
1099  }
1100  else
1101  {
1102  *(p1 - 1) = 0; // trim the final '/'
1103  }
1104 
1105  p1[0] = '*';
1106 #ifdef WIN32
1107  p1[1] = '.';
1108  p1[2] = '*';
1109  p1[3] = 0;
1110 #else // !WIN32
1111  p1[1] = 0;
1112 #endif // WIN32
1113  }
1114  else if(strchr(p1, '*') || strchr(p1, '?'))
1115  {
1116  if(p1 == (pBuf + 1) && *pBuf == '/') // root dir
1117  {
1118  memmove(p1 + 1, p1, strlen(p1) + 1);
1119  *(p1++) = 0; // after this, p1 points to the file spec
1120  }
1121  else
1122  {
1123  *(p1 - 1) = 0; // p1 points to the file spec
1124  }
1125  }
1126  else if(WBIsDirectory(pBuf)) // entire name is a directory
1127  {
1128  // NOTE: root directory should NEVER end up here
1129 
1130  p1 += strlen(p1);
1131  *(p1++) = 0; // end of path (would be '/')
1132  p1[0] = '*';
1133 #ifdef WIN32
1134  p1[1] = '.';
1135  p1[2] = '*';
1136  p1[3] = 0;
1137 #else // !WIN32
1138  p1[1] = 0;
1139 #endif // WIN32
1140  }
1141  else
1142  {
1143  WB_WARN_PRINT("TEMPORARY: I am confused, %s %s\n", pBuf, p1);
1144  }
1145  }
1146  else
1147  {
1148  // this should never happen if I'm always prepending a './'
1149  // TODO: make this more consistent, maybe absolute path?
1150 
1151  WB_WARN_PRINT("TEMPORARY: should not happen, %s %s\n", pBuf, p1);
1152 
1153  if(strchr(pBuf, '*') || strchr(pBuf, '?')) // wildcard spec
1154  {
1155  p1 = (char *)pBuf + 1; // make room for zero byte preceding dir spec
1156  memmove(pBuf, p1, iLen + 1);
1157  *pBuf = 0; // since it's the current working dir just make it a zero byte (empty string)
1158  }
1159  else if(WBIsDirectory(pBuf))
1160  {
1161  p1 = (char *)pBuf + iLen;
1162  *(p1++) = 0; // end of path (would be '/')
1163  p1[0] = '*';
1164 #ifdef WIN32
1165  p1[2] = '*';
1166  p1[3] = 0;
1167 #else // !WIN32
1168  p1[1] = 0;
1169 #endif // WIN32
1170  }
1171  }
1172 
1173  pRval = WBAlloc(sizeof(DIRLIST) + iLen + strlen(p1) + 2);
1174 
1175  if(pRval)
1176  {
1177  pRval->szPath = pBuf;
1178  pRval->szNameSpec = p1;
1179 
1180  p2 = (char *)(pRval + 1);
1181  strcpy(p2, pBuf);
1182  p2 += strlen(p2);
1183  *(p2++) = '/';
1184  strcpy(p2, p1);
1185  p1 = (char *)(pRval + 1);
1186 
1187 #ifdef WIN32
1188  pRval->hFF = FindFirstFile(p2, &(pRval->fd))
1189  if(pRval->hFF == INVALID_HANDLE_VALUE)
1190  {
1191  WBFree(pBuf);
1192  WBFree(pRval);
1193 
1194  pRval = NULL;
1195  }
1196 #else // !WIN32
1197  pRval->hD = opendir(pBuf);
1198 
1199 // WB_ERROR_PRINT("TEMPORARY - opendir for %s returns %p\n", pBuf, pRval->hD);
1200 
1201  if(pRval->hD == NULL)
1202  {
1203  WB_WARN_PRINT("WARNING - %s - Unable to open dir \"%s\", errno=%d\n", __FUNCTION__, pBuf, errno);
1204 
1205  WBFree(pBuf);
1206  WBFree(pRval);
1207 
1208  pRval = NULL;
1209  }
1210 #endif // WIN32,!WIN32
1211  }
1212  else
1213  {
1214  WB_ERROR_PRINT("ERROR - %s - Unable to allocate memory for DIRLIST\n", __FUNCTION__);
1215  WBFree(pBuf); // no need to keep this around
1216  }
1217 
1218  return pRval;
1219 }
1220 
1221 void WBDestroyDirectoryList(void *pDirectoryList)
1222 {
1223  if(pDirectoryList)
1224  {
1225  DIRLIST *pD = (DIRLIST *)pDirectoryList;
1226 
1227 #ifdef WIN32
1228  if(pD->hFF != INVALID_HANDLE_VALUE)
1229  {
1230  FindClose(pD->hFF);
1231  }
1232 #else // !WIN32
1233  if(pD->hD)
1234  {
1235  closedir(pD->hD);
1236  }
1237 #endif // WIN32,!WIN32
1238  if(pD->szPath)
1239  {
1240  WBFree((void *)(pD->szPath));
1241  }
1242 
1243  WBFree(pDirectoryList);
1244  }
1245 }
1246 
1247 // returns < 0 on error, > 0 on EOF, 0 for "found something"
1248 
1249 int WBNextDirectoryEntry(void *pDirectoryList, char *szNameReturn,
1250  int cbNameReturn, unsigned long *pdwModeAttrReturn)
1251 {
1252 #ifdef WIN32
1253 #else // WIN32
1254 struct dirent *pD;
1255 struct stat sF;
1256 #endif // WIN32
1257 char *p1, *pBuf;
1258 //static char *p2; // temporary
1259 int iRval = 1; // default 'EOF'
1260 DIRLIST *pDL = (DIRLIST *)pDirectoryList;
1261 
1262 
1263  if(!pDirectoryList)
1264  {
1265  return -1;
1266  }
1267 
1268  // TODO: improve this, maybe cache buffer or string length...
1269  pBuf = WBAlloc(strlen(pDL->szPath) + 8 + NAME_MAX);
1270 
1271  if(!pBuf)
1272  {
1273  return -2;
1274  }
1275 
1276  strcpy(pBuf, pDL->szPath);
1277  p1 = pBuf + strlen(pBuf);
1278  if(p1 > pBuf && *(p1 - 1) != '/') // it does not already end in /
1279  {
1280  *(p1++) = '/'; // for now assume this
1281  *p1 = 0; // by convention
1282  }
1283 
1284 #ifdef WIN32
1285 
1286  // for WIN32, copy 'previous' data first, then 'FindNextFile'. On EOF mark
1287  // as EOF so that next call will detect it.
1288 
1289 #else // !WIN32
1290 
1291  if(pDL->hD)
1292  {
1293  while((pD = readdir(pDL->hD))
1294  != NULL)
1295  {
1296  // skip '.' and '..'
1297  if(pD->d_name[0] == '.' &&
1298  (!pD->d_name[1] ||
1299  (pD->d_name[1] == '.' && !pD->d_name[2])))
1300  {
1301 // WB_ERROR_PRINT("TEMPORARY: skipping %s\n", pD->d_name);
1302  continue; // no '.' or '..'
1303  }
1304 
1305  strcpy(p1, pD->d_name);
1306 
1307  if(!lstat(pBuf, &sF)) // 'lstat' returns data about a file, and if it's a symlink, returns info about the link itself
1308  {
1309  if(!fnmatch(pDL->szNameSpec, p1, 0/*FNM_PERIOD*/)) // 'tbuf2' is my pattern
1310  {
1311  iRval = 0;
1312 
1313  if(pdwModeAttrReturn)
1314  {
1315  *pdwModeAttrReturn = sF.st_mode;
1316  }
1317 
1318  if(szNameReturn && cbNameReturn > 0)
1319  {
1320  strncpy(szNameReturn, p1, cbNameReturn);
1321  }
1322 
1323  break;
1324  }
1325 // else
1326 // {
1327 // p2 = pDL->szNameSpec;
1328 //
1329 // WB_ERROR_PRINT("TEMPORARY: \"%s\" does not match \"%s\"\n", p1, p2);
1330 // }
1331  }
1332  else
1333  {
1334  WB_WARN_PRINT("%s: can't 'stat' %s, errno=%d (%08xH)\n", __FUNCTION__, pBuf, errno, errno);
1335  }
1336  }
1337  }
1338 
1339 #endif // WIN32,!WIN32
1340 
1341  if(pBuf)
1342  {
1343  WBFree(pBuf);
1344  }
1345 
1346  return iRval;
1347 
1348 }
1349 
1350 char *WBGetDirectoryListFileFullPath(const void *pDirectoryList, const char *szFileName)
1351 {
1352 char *pRval, *pBuf, *p1;
1353 DIRLIST *pDL = (DIRLIST *)pDirectoryList;
1354 
1355  if(!pDirectoryList)
1356  {
1357  if(!szFileName || !*szFileName)
1358  {
1359  return NULL;
1360  }
1361 
1362  return WBGetCanonicalPath(szFileName);
1363  }
1364 
1365  if(szFileName && *szFileName == '/')
1366  {
1367  return WBGetCanonicalPath(szFileName); // don't need relative path
1368  }
1369 
1370  // TODO: improve this, maybe cache buffer or string length...
1371  pBuf = (char *)WBAlloc(strlen(pDL->szPath) + 8 + (szFileName ? strlen(szFileName) : 0) + NAME_MAX);
1372 
1373  if(!pBuf)
1374  {
1375  return NULL;
1376  }
1377 
1378  strcpy(pBuf, pDL->szPath);
1379  p1 = pBuf + strlen(pBuf);
1380  if(p1 > pBuf && *(p1 - 1) != '/') // ends in a slash?
1381  {
1382  *(p1++) = '/'; // for now assume this
1383  *p1 = 0; // by convention (though probably not necessary)
1384  }
1385 
1386  if(szFileName)
1387  {
1388  strcpy(p1, szFileName);
1389  }
1390 
1391  pRval = WBGetCanonicalPath(pBuf);
1392  WBFree(pBuf);
1393 
1394  return pRval;
1395 }
1396 
1397 char *WBGetSymLinkTarget(const char *szFileName)
1398 {
1399 char *pRval = WBAlloc(MAXPATHLEN + 2);
1400 
1401  if(pRval)
1402  {
1403  int iLen = readlink(szFileName, pRval, MAXPATHLEN);
1404  if(iLen <= 0)
1405  {
1406  WBFree(pRval);
1407  return NULL;
1408  }
1409 
1410  pRval[iLen] = 0; // assume < MAXPATHLEN for now...
1411  }
1412 
1413  return pRval;
1414 }
1415 
1416 char *WBGetDirectoryListSymLinkTarget(const void *pDirectoryList, const char *szFileName)
1417 {
1418 char *pTemp, *pRval;
1419 
1420  pTemp = WBGetDirectoryListFileFullPath(pDirectoryList, szFileName);
1421 
1422  if(!pTemp)
1423  {
1424  return NULL;
1425  }
1426 
1427  pRval = WBGetSymLinkTarget(pTemp);
1428  WBFree(pTemp);
1429 
1430  return pRval;
1431 }
1432 
1433 int WBStat(const char *szLinkName, unsigned long *pdwModeAttrReturn)
1434 {
1435 int iRval;
1436 struct stat sF;
1437 
1438 
1439  iRval = stat(szLinkName, &sF);
1440  if(!iRval && pdwModeAttrReturn)
1441  {
1442  *pdwModeAttrReturn = sF.st_mode;
1443  }
1444 
1445  return iRval; // zero on success
1446 }
1447 
1448 int WBGetDirectoryListFileStat(const void *pDirectoryList, const char *szFileName,
1449  unsigned long *pdwModeAttrReturn)
1450 {
1451 char *pTemp;
1452 int iRval;
1453 
1454  pTemp = WBGetDirectoryListFileFullPath(pDirectoryList, szFileName);
1455 
1456  if(!pTemp)
1457  {
1458  return -1;
1459  }
1460 
1461 // WB_ERROR_PRINT("TEMPORARY: stat on '%s' - \"%s\"\n", szFileName, pTemp);
1462 
1463  iRval = WBStat(pTemp, pdwModeAttrReturn);
1464  WBFree(pTemp);
1465 
1466  return iRval;
1467 }
1468