debug_io_flat.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. /*
  2. ** Command & Conquer Generals Zero Hour(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /////////////////////////////////////////////////////////////////////////EA-V1
  19. // $File: //depot/GeneralsMD/Staging/code/Libraries/Source/debug/debug_io_flat.cpp $
  20. // $Author: mhoffe $
  21. // $Revision: #1 $
  22. // $DateTime: 2003/07/03 11:55:26 $
  23. //
  24. // ©2003 Electronic Arts
  25. //
  26. // Debug I/O class flat (flat or split log file)
  27. //////////////////////////////////////////////////////////////////////////////
  28. #include "_pch.h"
  29. #include <stdlib.h>
  30. #include <new> // needed for placement new prototype
  31. DebugIOFlat::OutputStream::OutputStream(const char *filename, unsigned maxSize):
  32. m_bufferUsed(0), m_nextChar(0)
  33. {
  34. m_fileName=(char *)DebugAllocMemory(strlen(filename)+1);
  35. strcpy(m_fileName,filename);
  36. m_limitedFileSize=maxSize>0;
  37. m_bufferSize=m_limitedFileSize?maxSize:0x10000;
  38. m_buffer=(char *)DebugAllocMemory(m_bufferSize);
  39. if (!m_limitedFileSize)
  40. m_fileHandle=CreateFile(m_fileName,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
  41. FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
  42. NULL);
  43. }
  44. DebugIOFlat::OutputStream::~OutputStream()
  45. {
  46. }
  47. DebugIOFlat::OutputStream *DebugIOFlat::OutputStream::Create(const char *filename, unsigned maxSize)
  48. {
  49. return new (DebugAllocMemory(sizeof(OutputStream))) OutputStream(filename,maxSize);
  50. }
  51. void DebugIOFlat::OutputStream::Delete(const char *path)
  52. {
  53. Flush();
  54. if (!m_limitedFileSize)
  55. CloseHandle(m_fileHandle);
  56. if (path&&*path)
  57. {
  58. // copy file to given path
  59. int run=-1;
  60. char *ext=strrchr(m_fileName,'.');
  61. if (!ext)
  62. ext=m_fileName+strlen(m_fileName);
  63. char *fileNameOnly=strrchr(m_fileName,'\\');
  64. fileNameOnly=fileNameOnly?fileNameOnly+1:m_fileName;
  65. unsigned pathLen=strlen(path);
  66. for (;;)
  67. {
  68. // absolute path?
  69. char help[512];
  70. if (path[0]&&(path[1]==':'||(path[0]=='\\'&&path[1]=='\\')))
  71. {
  72. strcpy(help,path);
  73. strcpy(help+pathLen,fileNameOnly);
  74. help[ext-fileNameOnly+pathLen]=0;
  75. }
  76. else
  77. {
  78. // no, relative path given
  79. strcpy(help,m_fileName);
  80. strcpy(help+(fileNameOnly-m_fileName),path);
  81. strcpy(help+(fileNameOnly-m_fileName)+pathLen,fileNameOnly);
  82. help[ext-fileNameOnly+pathLen+(fileNameOnly-m_fileName)]=0;
  83. }
  84. if (++run)
  85. wsprintf(help+strlen(help),"(%i)%s",run,ext);
  86. else
  87. strcat(help,ext);
  88. if (CopyFile(m_fileName,help,TRUE))
  89. break;
  90. if (GetLastError()!=ERROR_FILE_EXISTS)
  91. break;
  92. }
  93. }
  94. DebugFreeMemory(m_buffer);
  95. DebugFreeMemory(m_fileName);
  96. this->~OutputStream();
  97. DebugFreeMemory(this);
  98. }
  99. void DebugIOFlat::OutputStream::Write(const char *src)
  100. {
  101. if (!src)
  102. {
  103. // flush request, flush only if unlimited file size
  104. if (!m_limitedFileSize)
  105. Flush();
  106. }
  107. else
  108. {
  109. unsigned len=strlen(src);
  110. while (len>m_bufferSize)
  111. {
  112. InternalWrite(src,m_bufferSize);
  113. src+=m_bufferSize;
  114. len-=m_bufferSize;
  115. }
  116. InternalWrite(src,len);
  117. }
  118. }
  119. void DebugIOFlat::OutputStream::InternalWrite(const char *src, unsigned len)
  120. {
  121. __ASSERT(len<=m_bufferSize);
  122. // unlimited log file length?
  123. if (!m_limitedFileSize)
  124. {
  125. if (m_bufferUsed+len>m_bufferSize)
  126. Flush();
  127. memcpy(m_buffer+m_bufferUsed,src,len);
  128. m_bufferUsed+=len;
  129. }
  130. else
  131. {
  132. // just write to ring buffer
  133. if ((m_bufferUsed+=len)>m_bufferSize)
  134. m_bufferUsed=m_bufferSize;
  135. while (len)
  136. {
  137. unsigned toWrite;
  138. if (m_nextChar+len>m_bufferSize)
  139. toWrite=m_bufferSize-m_nextChar;
  140. else
  141. toWrite=len;
  142. memcpy(m_buffer+m_nextChar,src,toWrite);
  143. if ((m_nextChar+=toWrite)>=m_bufferSize)
  144. m_nextChar=0;
  145. src+=toWrite;
  146. len-=toWrite;
  147. }
  148. }
  149. }
  150. void DebugIOFlat::OutputStream::Flush(void)
  151. {
  152. if (!m_limitedFileSize)
  153. {
  154. // simple flush to file
  155. DWORD written;
  156. WriteFile(m_fileHandle,m_buffer,m_bufferUsed,&written,NULL);
  157. m_bufferUsed=0;
  158. }
  159. else
  160. {
  161. // create file, write ring buffer
  162. m_fileHandle=CreateFile(m_fileName,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
  163. FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
  164. NULL);
  165. DWORD written;
  166. if (m_bufferUsed<m_bufferSize)
  167. WriteFile(m_fileHandle,m_buffer,m_bufferUsed,&written,NULL);
  168. else
  169. {
  170. WriteFile(m_fileHandle,m_buffer+m_nextChar,m_bufferUsed-m_nextChar,&written,NULL);
  171. WriteFile(m_fileHandle,m_buffer,m_nextChar,&written,NULL);
  172. }
  173. CloseHandle(m_fileHandle);
  174. }
  175. }
  176. //////////////////////////////////////////////////////////////////////////////
  177. void DebugIOFlat::ExpandMagic(const char *src, const char *splitName, char *buf)
  178. {
  179. // barf if too long
  180. if (strlen(src)>250)
  181. src="*eMN";
  182. // non-magic name?
  183. if (*src!='*')
  184. {
  185. // just return input name
  186. if (splitName)
  187. {
  188. // must jam in split name before extension
  189. const char *p=strrchr(src,'.');
  190. if (!p)
  191. p=src+strlen(src);
  192. strncpy(buf,src,p-src);
  193. buf[p-src]='-';
  194. strcpy(buf+(p-src)+1,splitName);
  195. strcat(buf,p);
  196. }
  197. else
  198. strcpy(buf,src);
  199. return;
  200. }
  201. // translate magic name
  202. src++;
  203. char *dst=buf;
  204. while (*src)
  205. {
  206. if (dst-buf>250)
  207. break;
  208. if (*src>='A'&&*src<='Z'&&(*src!='N'||splitName))
  209. *dst++='-';
  210. char help[256];
  211. DWORD size=sizeof(help);
  212. *help=0;
  213. switch(*src++)
  214. {
  215. case 'e':
  216. case 'E':
  217. GetModuleFileName(NULL,help,sizeof(help));
  218. break;
  219. case 'm':
  220. case 'M':
  221. GetComputerName(help,&size);
  222. break;
  223. case 'u':
  224. case 'U':
  225. GetUserName(help,&size);
  226. break;
  227. case 't':
  228. case 'T':
  229. {
  230. SYSTEMTIME systime;
  231. GetLocalTime(&systime);
  232. wsprintf(help,"%04i%02i%02i-%02i%02i-%02i",
  233. systime.wYear,systime.wMonth,systime.wDay,
  234. systime.wHour,systime.wMinute,systime.wSecond);
  235. }
  236. break;
  237. case 'n':
  238. case 'N':
  239. if (splitName&&strlen(splitName)<250)
  240. strcpy(help,splitName);
  241. break;
  242. default:
  243. *dst++=src[-1];
  244. }
  245. unsigned len=strlen(help);
  246. if (dst-buf+len>250)
  247. break;
  248. strcpy(dst,help);
  249. dst+=len;
  250. }
  251. strcpy(dst,".log");
  252. }
  253. DebugIOFlat::DebugIOFlat(void):
  254. m_firstStream(NULL), m_firstSplit(NULL),
  255. m_lastStreamPtr(&m_firstStream), m_lastSplitPtr(&m_firstSplit)
  256. {
  257. *m_copyDir=0;
  258. }
  259. DebugIOFlat::~DebugIOFlat()
  260. {
  261. for (SplitListEntry *cur=m_firstSplit;cur;)
  262. {
  263. SplitListEntry *kill=cur;
  264. cur=cur->next;
  265. DebugFreeMemory(kill);
  266. }
  267. m_firstSplit=NULL;
  268. for (StreamListEntry *stream=m_firstStream;stream;)
  269. {
  270. StreamListEntry *kill=stream;
  271. stream=stream->next;
  272. kill->stream->Delete(m_copyDir);
  273. DebugFreeMemory(kill);
  274. }
  275. }
  276. void DebugIOFlat::Write(StringType type, const char *src, const char *str)
  277. {
  278. for (SplitListEntry *cur=m_firstSplit;cur;cur=cur->next)
  279. {
  280. if (!(cur->stringTypes&(1<<type)))
  281. continue;
  282. if (src&&*src&&!Debug::SimpleMatch(src,cur->items))
  283. continue;
  284. cur->stream->Write(str);
  285. break;
  286. }
  287. if (!cur)
  288. m_firstStream->stream->Write(str);
  289. }
  290. void DebugIOFlat::EmergencyFlush(void)
  291. {
  292. for (StreamListEntry *cur=m_firstStream;cur;cur=cur->next)
  293. cur->stream->Flush();
  294. }
  295. void DebugIOFlat::Execute(class Debug& dbg, const char *cmd, bool structuredCmd,
  296. unsigned argn, const char * const * argv)
  297. {
  298. if (!cmd||!strcmp(cmd,"help"))
  299. {
  300. if (!argn)
  301. dbg << "flat I/O help:\n"
  302. "The following I/O commands are defined:\n"
  303. " add, copy, splitadd, splitview, splitremove\n"
  304. "Type in debug.io flat help <cmd> for a detailed command help.\n";
  305. else if (!strcmp(argv[0],"add"))
  306. dbg <<
  307. "add [ <filename> [ <size in kb> ] ]\n\n"
  308. "Create flat file I/O (optionally specifying file name and file size).\n"
  309. "If a filename is specified all output is written to that file. Otherwise\n"
  310. "the magic filename '*eMN' is automatically used. Any existing files with\n"
  311. "that name are overwritten.\n"
  312. "\n"
  313. "Instead of a real file name a 'magic' file name can be used by starting\n"
  314. "the file name with a '*' followed by any number of special characters:\n"
  315. "- 'e': inserts EXE name\n"
  316. "- 'm': inserts machine name\n"
  317. "- 'u': inserts username\n"
  318. "- 't': inserts timestamp\n"
  319. "- 'n': inserts split name (empty if main log file)\n"
  320. "- '-': inserts '-'\n"
  321. "- 'E', 'M', 'U', 'T', 'N': same as above but with '-' in front \n"
  322. " (for 'N': empty if main log file)\n"
  323. ".log is automatically appended if using magic file names.\n"
  324. "\n"
  325. "If a size is specified then all data is internally written to a fixed \n"
  326. "size memory based ring buffer. This data is flushed out once the \n"
  327. "program exits. If no size is given then the size of the log file is not \n"
  328. "limited and any log data is written out immediately.\n";
  329. else if (!strcmp(argv[0],"copy"))
  330. dbg <<
  331. "copy <directory>\n\n"
  332. "Copies generated log file(s) into the given directory if the program\n"
  333. "exists or crashes. If there is already a log file with the same\n"
  334. "name a unique number is appended to the current log files' name.\n";
  335. else if (!strcmp(argv[0],"splitadd"))
  336. dbg <<
  337. "splitadd <types> <filter> <name> [ <size in kb> ]\n\n"
  338. "Splits off part of the log data. Multiple splits can be defined. They \n"
  339. "are written out to the first matching split file.\n"
  340. "\n"
  341. "'types' defines one or more string types which should be split off:\n"
  342. "- a: asserts\n"
  343. "- c: checks\n"
  344. "- l: logs\n"
  345. "- h: crash\n"
  346. "- x: exceptions\n"
  347. "- r: replies from commands\n"
  348. "- o: other messages \n"
  349. "\n"
  350. "Next a filter is specified that determines which items are to be \n"
  351. "filtered (only for string types a, c and l). Items can be listed with\n"
  352. "then 'list' command. The filter is exactly specified as in that command.\n"
  353. "\n"
  354. "The third parameter defines a name for this split. If there is \n"
  355. "already a split with the same name then both will write to the same \n"
  356. "destination file.\n"
  357. "\n"
  358. "Note: If splits are used and the filename given for 'add' is static \n"
  359. "or does not contain 'n' or 'N' then the split name is automatically \n"
  360. "appended to the log file name (before the extension).\n"
  361. "\n"
  362. "If a size is specified then all data is internally written to a \n"
  363. "fixed size memory based ring buffer. This data is flushed out once \n"
  364. "the program exits.\n"
  365. "\n"
  366. "If no size is given then the size of the log file is not limited and \n"
  367. "any log data is written out immediately.\n";
  368. else if (!strcmp(argv[0],"splitview"))
  369. dbg << "splitview\n\n"
  370. "Shows all existing splits in the order they are evaluated.";
  371. else if (!strcmp(argv[0],"splitremove"))
  372. dbg << "splitremove <namepattern>\n\n"
  373. "Removes all active splits matching the given name pattern.";
  374. else
  375. dbg << "Unknown flat I/O command";
  376. }
  377. else if (!strcmp(cmd,"add"))
  378. {
  379. // add [ <filename> [ <size in kb> ] ]
  380. __ASSERT(m_firstStream==NULL);
  381. strcpy(m_baseFilename,argn?argv[0]:"*eMN");
  382. char fn[256];
  383. ExpandMagic(m_baseFilename,NULL,fn);
  384. m_firstStream=(StreamListEntry *)DebugAllocMemory(sizeof(StreamListEntry));
  385. m_firstStream->next=NULL;
  386. m_firstStream->stream=OutputStream::Create(fn,argn>1?atoi(argv[1])*1024:0);
  387. m_lastStreamPtr=&m_firstStream->next;
  388. }
  389. else if (!strcmp(cmd,"copy"))
  390. {
  391. // copy <directory>
  392. if (argn)
  393. {
  394. strncpy(m_copyDir,argv[0],sizeof(m_copyDir)-1);
  395. m_copyDir[sizeof(m_copyDir)-1]=0;
  396. }
  397. }
  398. else if (!strcmp(cmd,"splitadd"))
  399. {
  400. // splitadd <types> <filter> <name> [ <size in kb> ]
  401. if (argn>=3)
  402. {
  403. // add entry
  404. SplitListEntry *cur=(SplitListEntry *)DebugAllocMemory(sizeof(SplitListEntry));
  405. cur->next=*m_lastSplitPtr;
  406. m_lastSplitPtr=&cur->next;
  407. if (!m_firstSplit)
  408. m_firstSplit=cur;
  409. cur->stringTypes=0;
  410. for (const char *p=argv[0];*p;++p)
  411. {
  412. switch(*p)
  413. {
  414. case 'a': cur->stringTypes|=1<<Assert; break;
  415. case 'c': cur->stringTypes|=1<<Check; break;
  416. case 'l': cur->stringTypes|=1<<Log; break;
  417. case 'h': cur->stringTypes|=1<<Crash; break;
  418. case 'x': cur->stringTypes|=1<<Exception; break;
  419. case 'r': cur->stringTypes|=1<<CmdReply; break;
  420. case 'o': cur->stringTypes|=1<<Other; break;
  421. }
  422. }
  423. if (!cur->stringTypes)
  424. cur->stringTypes=0xffffffff;
  425. strncpy(cur->items,argv[1],sizeof(cur->items)-1);
  426. cur->items[sizeof(cur->items)-1]=0;
  427. strncpy(cur->name,argv[2],sizeof(cur->name)-1);
  428. cur->name[sizeof(cur->name)-1]=0;
  429. // create our filename, search for stream with same filename
  430. char fn[256];
  431. ExpandMagic(m_baseFilename,cur->name,fn);
  432. for (StreamListEntry *stream=m_firstStream;stream;stream=stream->next)
  433. if (!strcmp(stream->stream->GetFilename(),fn))
  434. break;
  435. if (!stream)
  436. {
  437. // must create new stream
  438. stream=(StreamListEntry *)DebugAllocMemory(sizeof(StreamListEntry));
  439. stream->next=NULL;
  440. *m_lastStreamPtr=stream;
  441. m_lastStreamPtr=&stream->next;
  442. stream->stream=OutputStream::Create(fn,argn>3?atoi(argv[3])*1024:0);
  443. }
  444. cur->stream=stream->stream;
  445. }
  446. }
  447. else if (!strcmp(cmd,"splitview"))
  448. {
  449. // splitview
  450. for (SplitListEntry *cur=m_firstSplit;cur;cur=cur->next)
  451. {
  452. for (StringType t=Assert;t<MAX;t=(StringType)(t+1))
  453. {
  454. if (t==StructuredCmdReply||!(cur->stringTypes&(1<<t)))
  455. continue;
  456. switch(t)
  457. {
  458. case Assert: dbg << "a"; break;
  459. case Check: dbg << "c"; break;
  460. case Log: dbg << "l"; break;
  461. case Crash: dbg << "h"; break;
  462. case Exception: dbg << "x"; break;
  463. case CmdReply: dbg << "r"; break;
  464. case Other: dbg << "o"; break;
  465. }
  466. }
  467. dbg << " " << cur->items << " " << cur->name << "\n";
  468. }
  469. }
  470. else if (!strcmp(cmd,"splitremove"))
  471. {
  472. // splitremove <namepattern>
  473. const char *pattern=argn<1?"*":argv[0];
  474. for (SplitListEntry **entryPtr=&m_firstSplit;*entryPtr;)
  475. {
  476. if ( Debug::SimpleMatch((*entryPtr)->name,pattern) )
  477. {
  478. // remove this entry
  479. SplitListEntry *cur=*entryPtr;
  480. *entryPtr=cur->next;
  481. DebugFreeMemory(cur);
  482. }
  483. else
  484. entryPtr=&((*entryPtr)->next);
  485. }
  486. // must fixup m_lastSplitPtr now
  487. if (m_firstSplit)
  488. {
  489. for (SplitListEntry *cur=m_firstSplit;cur->next;cur=cur->next);
  490. m_firstSplit=cur;
  491. }
  492. else
  493. m_firstSplit=NULL;
  494. }
  495. }
  496. DebugIOInterface *DebugIOFlat::Create(void)
  497. {
  498. return new (DebugAllocMemory(sizeof(DebugIOFlat))) DebugIOFlat();
  499. }
  500. void DebugIOFlat::Delete(void)
  501. {
  502. this->~DebugIOFlat();
  503. DebugFreeMemory(this);
  504. }