1 /******************************************************************************
2  * $Id$
3  *
4  * Project:  MapServer
5  * Purpose:  Implementation of debug/logging, msDebug() and related functions.
6  * Author:   Daniel Morissette
7  *
8  ******************************************************************************
9  * Copyright (c) 1996-2007 Regents of the University of Minnesota.
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included in
19  * all copies of this Software or works derived from this Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  ****************************************************************************/
29 
30 
31 #include "mapserver.h"
32 #include "maperror.h"
33 #include "mapthread.h"
34 #include "maptime.h"
35 
36 #include <time.h>
37 #ifndef _WIN32
38 #include <sys/time.h>
39 #include <unistd.h>
40 #endif
41 #include <stdarg.h>
42 
43 #ifdef NEED_NONBLOCKING_STDERR
44 #include <fcntl.h>
45 #endif
46 
47 #ifdef _WIN32
48 #include <windows.h> /* OutputDebugStringA() */
49 #endif
50 
51 
52 
53 
54 #ifndef USE_THREAD
55 
msGetDebugInfoObj()56 debugInfoObj *msGetDebugInfoObj()
57 {
58   static debugInfoObj debuginfo = {MS_DEBUGLEVEL_ERRORSONLY,
59                                    MS_DEBUGMODE_OFF, NULL, NULL
60                                   };
61   return &debuginfo;
62 }
63 
64 #else
65 
66 static debugInfoObj *debuginfo_list = NULL;
67 
msGetDebugInfoObj()68 debugInfoObj *msGetDebugInfoObj()
69 {
70   debugInfoObj *link;
71   void* thread_id;
72   debugInfoObj *ret_obj;
73 
74   msAcquireLock( TLOCK_DEBUGOBJ );
75 
76   thread_id = msGetThreadId();
77 
78   /* find link for this thread */
79 
80   for( link = debuginfo_list;
81        link != NULL && link->thread_id != thread_id
82        && link->next != NULL && link->next->thread_id != thread_id;
83        link = link->next ) {}
84 
85   /* If the target thread link is already at the head of the list were ok */
86   if( debuginfo_list != NULL && debuginfo_list->thread_id == thread_id ) {
87   }
88 
89   /* We don't have one ... initialize one. */
90   else if( link == NULL || link->next == NULL ) {
91     debugInfoObj *new_link;
92 
93     new_link = (debugInfoObj *) msSmallMalloc(sizeof(debugInfoObj));
94     new_link->next = debuginfo_list;
95     new_link->thread_id = thread_id;
96     new_link->global_debug_level = MS_DEBUGLEVEL_ERRORSONLY;
97     new_link->debug_mode = MS_DEBUGMODE_OFF;
98     new_link->errorfile = NULL;
99     new_link->fp = NULL;
100     debuginfo_list = new_link;
101   }
102 
103   /* If the link is not already at the head of the list, promote it */
104   else if( link != NULL && link->next != NULL ) {
105     debugInfoObj *target = link->next;
106 
107     link->next = link->next->next;
108     target->next = debuginfo_list;
109     debuginfo_list = target;
110   }
111 
112   ret_obj = debuginfo_list;
113 
114   msReleaseLock( TLOCK_DEBUGOBJ );
115 
116   return ret_obj;
117 }
118 #endif
119 
120 
121 /* msSetErrorFile()
122 **
123 ** Set output target, ready to write to it, open file if necessary
124 **
125 ** If pszRelToPath != NULL then we will try to make the value relative to
126 ** this path if it is not absolute already and it's not one of the special
127 ** values (stderr, stdout, windowsdebug)
128 **
129 ** Returns MS_SUCCESS/MS_FAILURE
130 */
msSetErrorFile(const char * pszErrorFile,const char * pszRelToPath)131 int msSetErrorFile(const char *pszErrorFile, const char *pszRelToPath)
132 {
133   char extended_path[MS_MAXPATHLEN];
134   debugInfoObj *debuginfo = msGetDebugInfoObj();
135 
136   if (strcmp(pszErrorFile, "stderr") != 0 &&
137       strcmp(pszErrorFile, "stdout") != 0 &&
138       strcmp(pszErrorFile, "windowsdebug") != 0) {
139     /* Try to make the path relative */
140     if(msBuildPath(extended_path, pszRelToPath, pszErrorFile) == NULL)
141       return MS_FAILURE;
142     pszErrorFile = extended_path;
143   }
144 
145   if (debuginfo->errorfile && pszErrorFile &&
146       strcmp(debuginfo->errorfile, pszErrorFile) == 0) {
147     /* Nothing to do, already writing to the right place */
148     return MS_SUCCESS;
149   }
150 
151   /* Close current output file if any */
152   msCloseErrorFile();
153 
154   /* NULL or empty target will just close current output and return */
155   if (pszErrorFile == NULL || *pszErrorFile == '\0')
156     return MS_SUCCESS;
157 
158   if (strcmp(pszErrorFile, "stderr") == 0) {
159     debuginfo->fp = stderr;
160     debuginfo->errorfile = msStrdup(pszErrorFile);
161     debuginfo->debug_mode = MS_DEBUGMODE_STDERR;
162   } else if (strcmp(pszErrorFile, "stdout") == 0) {
163     debuginfo->fp = stdout;
164     debuginfo->errorfile = msStrdup(pszErrorFile);
165     debuginfo->debug_mode = MS_DEBUGMODE_STDOUT;
166   } else if (strcmp(pszErrorFile, "windowsdebug") == 0) {
167 #ifdef _WIN32
168     debuginfo->errorfile = msStrdup(pszErrorFile);
169     debuginfo->fp = NULL;
170     debuginfo->debug_mode = MS_DEBUGMODE_WINDOWSDEBUG;
171 #else
172     msSetError(MS_MISCERR, "'MS_ERRORFILE windowsdebug' is available only on Windows platforms.", "msSetErrorFile()");
173     return MS_FAILURE;
174 #endif
175   } else {
176     debuginfo->fp = fopen(pszErrorFile, "a");
177     if (debuginfo->fp == NULL) {
178       msSetError(MS_MISCERR, "Failed to open MS_ERRORFILE %s", "msSetErrorFile()", pszErrorFile);
179       return MS_FAILURE;
180     }
181     debuginfo->errorfile = msStrdup(pszErrorFile);
182     debuginfo->debug_mode = MS_DEBUGMODE_FILE;
183   }
184 
185   return MS_SUCCESS;
186 }
187 
188 /* msCloseErrorFile()
189 **
190 ** Close current output file (if one is open) and reset related members
191 */
msCloseErrorFile()192 void msCloseErrorFile()
193 {
194   debugInfoObj *debuginfo = msGetDebugInfoObj();
195 
196   if (debuginfo && debuginfo->debug_mode != MS_DEBUGMODE_OFF) {
197     if (debuginfo->fp && debuginfo->debug_mode == MS_DEBUGMODE_FILE)
198       fclose(debuginfo->fp);
199 
200     if (debuginfo->fp && (debuginfo->debug_mode == MS_DEBUGMODE_STDERR ||
201                           debuginfo->debug_mode == MS_DEBUGMODE_STDOUT))
202       fflush(debuginfo->fp); /* just flush stderr or stdout */
203 
204     debuginfo->fp = NULL;
205 
206     msFree(debuginfo->errorfile);
207     debuginfo->errorfile = NULL;
208 
209     debuginfo->debug_mode = MS_DEBUGMODE_OFF;
210   }
211 }
212 
213 
214 
215 /* msGetErrorFile()
216 **
217 ** Returns name of current error file
218 **
219 ** Returns NULL if not set.
220 */
msGetErrorFile()221 const char *msGetErrorFile()
222 {
223   debugInfoObj *debuginfo = msGetDebugInfoObj();
224 
225   if (debuginfo)
226     return debuginfo->errorfile;
227 
228   return NULL;
229 }
230 
231 /* Set/Get the current global debug level value, used as default value for
232 ** new map and layer objects and to control msDebug() calls outside of
233 ** the context of mapObj or layerObj.
234 **
235 */
msSetGlobalDebugLevel(int level)236 void msSetGlobalDebugLevel(int level)
237 {
238   debugInfoObj *debuginfo = msGetDebugInfoObj();
239 
240   if (debuginfo)
241     debuginfo->global_debug_level = (debugLevel)level;
242 }
243 
msGetGlobalDebugLevel()244 debugLevel msGetGlobalDebugLevel()
245 {
246   debugInfoObj *debuginfo = msGetDebugInfoObj();
247 
248   if (debuginfo)
249     return debuginfo->global_debug_level;
250 
251   return MS_DEBUGLEVEL_ERRORSONLY;
252 }
253 
254 
255 /* msDebugInitFromEnv()
256 **
257 ** Init debug state from MS_ERRORFILE and MS_DEBUGLEVEL env vars if set
258 **
259 ** Returns MS_SUCCESS/MS_FAILURE
260 */
msDebugInitFromEnv()261 int msDebugInitFromEnv()
262 {
263   const char *val;
264 
265   if( (val=getenv( "MS_ERRORFILE" )) != NULL ) {
266     if ( msSetErrorFile(val, NULL) != MS_SUCCESS )
267       return MS_FAILURE;
268   }
269 
270   if( (val=getenv( "MS_DEBUGLEVEL" )) != NULL )
271     msSetGlobalDebugLevel(atoi(val));
272 
273   return MS_SUCCESS;
274 }
275 
276 
277 /* msDebugCleanup()
278 **
279 ** Called by msCleanup to remove info related to this thread.
280 */
msDebugCleanup()281 void msDebugCleanup()
282 {
283   /* make sure file is closed */
284   msCloseErrorFile();
285 
286 #ifdef USE_THREAD
287   {
288     void*  thread_id = msGetThreadId();
289     debugInfoObj *link;
290 
291     msAcquireLock( TLOCK_DEBUGOBJ );
292 
293     /* find link for this thread */
294 
295     for( link = debuginfo_list;
296          link != NULL && link->thread_id != thread_id
297          && link->next != NULL && link->next->thread_id != thread_id;
298          link = link->next ) {}
299 
300     if( link->thread_id == thread_id ) {
301       /* presumably link is at head of list.  */
302       if( debuginfo_list == link )
303         debuginfo_list = link->next;
304 
305       free( link );
306     } else if( link->next != NULL && link->next->thread_id == thread_id ) {
307       debugInfoObj *next_link = link->next;
308       link->next = link->next->next;
309       free( next_link );
310     }
311     msReleaseLock( TLOCK_DEBUGOBJ );
312   }
313 #endif
314 
315 }
316 
317 /* msDebug()
318 **
319 ** Outputs/logs messages to the MS_ERRORFILE if one is set
320 ** (see msSetErrorFile())
321 **
322 */
msDebug(const char * pszFormat,...)323 void msDebug( const char * pszFormat, ... )
324 {
325   va_list args;
326   debugInfoObj *debuginfo = msGetDebugInfoObj();
327 
328   if (debuginfo == NULL || debuginfo->debug_mode == MS_DEBUGMODE_OFF)
329     return;  /* Don't waste time here! */
330 
331   if (debuginfo->fp) {
332     /* Writing to a stdio file handle */
333 
334 #if defined(USE_FASTCGI)
335     /* It seems the FastCGI stuff inserts a timestamp anyways, so  */
336     /* we might as well skip this one if writing to stderr w/ FastCGI. */
337     if (debuginfo->debug_mode != MS_DEBUGMODE_STDERR)
338 #endif
339     {
340       struct mstimeval tv;
341       time_t t;
342       msGettimeofday(&tv, NULL);
343       t = tv.tv_sec;
344       msIO_fprintf(debuginfo->fp, "[%s].%ld ",
345                    msStringChop(ctime(&t)), (long)tv.tv_usec);
346     }
347 
348     va_start(args, pszFormat);
349     msIO_vfprintf(debuginfo->fp, pszFormat, args);
350     va_end(args);
351   }
352 #ifdef _WIN32
353   else if (debuginfo->debug_mode == MS_DEBUGMODE_WINDOWSDEBUG) {
354     /* Writing to Windows Debug Console */
355 
356     char szMessage[MESSAGELENGTH];
357 
358     va_start(args, pszFormat);
359     vsnprintf( szMessage, MESSAGELENGTH, pszFormat, args );
360     va_end(args);
361 
362     szMessage[MESSAGELENGTH-1] = '\0';
363     OutputDebugStringA(szMessage);
364   }
365 #endif
366 
367 }
368 
369 
370 /* msDebug2()
371 **
372 ** Variadic function with no operation
373 **
374 */
msDebug2(int level,...)375 void msDebug2( int level, ... )
376 {
377 }
378