DABC (Data Acquisition Backbone Core)  2.9.9
Server.cxx
Go to the documentation of this file.
1 // $Id: Server.cxx 4655 2020-12-11 16:29:39Z linev $
2 
3 /************************************************************
4  * The Data Acquisition Backbone Core (DABC) *
5  ************************************************************
6  * Copyright (C) 2009 - *
7  * GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
8  * Planckstr. 1, 64291 Darmstadt, Germany *
9  * Contact: http://dabc.gsi.de *
10  ************************************************************
11  * This software can be used under the GPL license *
12  * agreements as stated in LICENSE.txt file *
13  * which is part of the distribution. *
14  ************************************************************/
15 
16 #include "http/Server.h"
17 
18 #include <cstring>
19 
20 #include "dabc/Url.h"
21 #include "dabc/Publisher.h"
22 
23 #ifndef DABC_WITHOUT_ZLIB
24 #include "zlib.h"
25 #endif
26 
27 const char* http::Server::GetMimeType(const char* path)
28 {
29  static const struct {
30  const char *extension;
31  int ext_len;
32  const char *mime_type;
33  } builtin_mime_types[] = {
34  {".xml", 4, "text/xml"},
35  {".json", 5, "application/json"},
36  {".bin", 4, "application/x-binary"},
37  {".gif", 4, "image/gif"},
38  {".jpg", 4, "image/jpeg"},
39  {".png", 4, "image/png"},
40  {".html", 5, "text/html"},
41  {".htm", 4, "text/html"},
42  {".shtm", 5, "text/html"},
43  {".shtml", 6, "text/html"},
44  {".css", 4, "text/css"},
45  {".js", 3, "application/x-javascript"},
46  {".ico", 4, "image/x-icon"},
47  {".jpeg", 5, "image/jpeg"},
48  {".svg", 4, "image/svg+xml"},
49  {".txt", 4, "text/plain"},
50  {".torrent", 8, "application/x-bittorrent"},
51  {".wav", 4, "audio/x-wav"},
52  {".mp3", 4, "audio/x-mp3"},
53  {".mid", 4, "audio/mid"},
54  {".m3u", 4, "audio/x-mpegurl"},
55  {".ogg", 4, "application/ogg"},
56  {".ram", 4, "audio/x-pn-realaudio"},
57  {".xslt", 5, "application/xml"},
58  {".xsl", 4, "application/xml"},
59  {".ra", 3, "audio/x-pn-realaudio"},
60  {".doc", 4, "application/msword"},
61  {".exe", 4, "application/octet-stream"},
62  {".zip", 4, "application/x-zip-compressed"},
63  {".xls", 4, "application/excel"},
64  {".tgz", 4, "application/x-tar-gz"},
65  {".tar", 4, "application/x-tar"},
66  {".gz", 3, "application/x-gunzip"},
67  {".arj", 4, "application/x-arj-compressed"},
68  {".rar", 4, "application/x-arj-compressed"},
69  {".rtf", 4, "application/rtf"},
70  {".pdf", 4, "application/pdf"},
71  {".swf", 4, "application/x-shockwave-flash"},
72  {".mpg", 4, "video/mpeg"},
73  {".webm", 5, "video/webm"},
74  {".mpeg", 5, "video/mpeg"},
75  {".mov", 4, "video/quicktime"},
76  {".mp4", 4, "video/mp4"},
77  {".m4v", 4, "video/x-m4v"},
78  {".asf", 4, "video/x-ms-asf"},
79  {".avi", 4, "video/x-msvideo"},
80  {".bmp", 4, "image/bmp"},
81  {".ttf", 4, "application/x-font-ttf"},
82  {NULL, 0, NULL}
83  };
84 
85  int path_len = strlen(path);
86 
87  for (int i = 0; builtin_mime_types[i].extension != NULL; i++) {
88  if (path_len <= builtin_mime_types[i].ext_len) continue;
89  const char* ext = path + (path_len - builtin_mime_types[i].ext_len);
90  if (strcmp(ext, builtin_mime_types[i].extension) == 0) {
91  return builtin_mime_types[i].mime_type;
92  }
93  }
94 
95  return "text/plain";
96 }
97 
98 
99 http::Server::Server(const std::string &name, dabc::Command cmd) :
100  dabc::Worker(MakePair(name)),
101  fLocations(),
102  fHttpSys(),
103  fJsRootSys(),
104  fDefaultAuth(-1)
105 {
106  fHttpSys = ".";
107 
108  const char* dabcsys = getenv("DABCSYS");
109  if (dabcsys!=0) {
110 
111  AddLocation(dabcsys, "dabcsys/");
112  AddLocation(dabcsys, "${DABCSYS}/");
113  AddLocation(dabc::format("%s/plugins/http", dabcsys), "httpsys/", "dabc_", "/files/");
114 
115  fJsRootSys = dabc::format("%s/plugins/root/js", dabcsys);
116 
117  fHttpSys = dabc::format("%s/plugins/http", dabcsys);
118  }
119 
120  std::string urlopt = cmd.GetStr("urlopt");
121  if (!urlopt.empty()) {
122  dabc::Url url;
123  url.SetOptions(urlopt);
124  unsigned cnt = 0;
125 
126  while (cnt<100) {
127  std::string name = dabc::format("loc%u", cnt++);
128  if (!url.HasOption(name+"d") || !url.HasOption(name+"a")) break;
129  AddLocation(url.GetOptionStr(name+"d"), url.GetOptionStr(name+"a"), url.GetOptionStr(name+"n"), url.GetOptionStr(name+"s"));
130  }
131  }
132 
133  const char* jsrootsys = getenv("JSROOTSYS");
134  if (jsrootsys!=0) fJsRootSys = jsrootsys;
135 
136  if (!fJsRootSys.empty()) {
137  AddLocation(fJsRootSys, "jsrootsys/", "root_", "/files/");
138  DOUT1("JSROOTSYS = %s ", fJsRootSys.c_str());
139  }
140 
141  DOUT1("HTTPSYS = %s", fHttpSys.c_str());
142 
143  fAutoLoad = Cfg("AutoLoad", cmd).AsStr("httpsys/scripts/dabc.js;httpsys/scripts/gauge.js;");
144  fTopTitle = Cfg("TopTitle", cmd).AsStr("DABC online server");
145  fBrowser = Cfg("Browser", cmd).AsStr("");
146  fLayout = Cfg("Layout", cmd).AsStr("");
147  fDrawItem = Cfg("DrawItem", cmd).AsStr("");
148  fDrawOpt = Cfg("DrawOpt", cmd).AsStr("");
149  fMonitoring = Cfg("Monitoring", cmd).AsInt(0);
150 }
151 
153 {
154 }
155 
156 void http::Server::AddLocation(const std::string &filepath,
157  const std::string &absprefix,
158  const std::string &nameprefix,
159  const std::string &nameprefixrepl)
160 {
161  fLocations.push_back(Location());
162  fLocations.back().fFilePath = filepath;
163  fLocations.back().fAbsPrefix = absprefix;
164  fLocations.back().fNamePrefix = nameprefix;
165  fLocations.back().fNamePrefixRepl = nameprefixrepl;
166 }
167 
168 
169 bool http::Server::VerifyFilePath(const char* fname)
170 {
171  if ((fname==0) || (*fname==0)) return false;
172 
173  int level = 0;
174 
175  while (*fname != 0) {
176 
177  // find next slash or backslash
178  const char* next = strpbrk(fname, "/\\");
179  if (next==0) return true;
180 
181  // most important - change to parent dir
182  if ((next == fname + 2) && (*fname == '.') && (*(fname+1) == '.')) {
183  fname += 3; level--;
184  if (level<0) return false;
185  continue;
186  }
187 
188  // ignore current directory
189  if ((next == fname + 1) && (*fname == '.')) {
190  fname += 2;
191  continue;
192  }
193 
194  // ignore slash at the front
195  if (next==fname) {
196  fname ++;
197  continue;
198  }
199 
200  fname = next+1;
201  level++;
202  }
203 
204  return true;
205 }
206 
207 bool http::Server::IsFileRequested(const char* uri, std::string& res)
208 {
209  if ((uri==0) || (strlen(uri)==0)) return false;
210 
211  std::string fname = uri;
212 
213  for (unsigned n=0;n<fLocations.size();n++) {
214  size_t pos = fname.rfind(fLocations[n].fAbsPrefix);
215  if (pos!=std::string::npos) {
216  fname.erase(0, pos + fLocations[n].fAbsPrefix.length() - 1);
217  if (!VerifyFilePath(fname.c_str())) return false;
218  res = fLocations[n].fFilePath + fname;
219  return true;
220  }
221  }
222 
223  return false;
224 }
225 
226 void http::Server::ExtractPathAndFile(const char* uri, std::string& pathname, std::string& filename)
227 {
228  pathname.clear();
229  const char* rslash = strrchr(uri,'/');
230  if (rslash == 0) {
231  filename = uri;
232  } else {
233  pathname.append(uri, rslash - uri);
234  if (pathname == "/") pathname.clear();
235  filename = rslash+1;
236  }
237 }
238 
239 
240 bool http::Server::IsAuthRequired(const char* uri)
241 {
242  if (fDefaultAuth<0) return false;
243 
244  std::string pathname, fname;
245 
246  if (IsFileRequested(uri, fname)) return fDefaultAuth > 0;
247 
248  ExtractPathAndFile(uri, pathname, fname);
249 
250  int res = dabc::PublisherRef(GetPublisher()).NeedAuth(pathname);
251 
252  // DOUT0("Request AUTH for path %s res = %d", pathname.c_str(), res);
253 
254  if (res<0) res = fDefaultAuth;
255 
256  return res > 0;
257 }
258 
259 bool http::Server::Process(const char* uri, const char* _query,
260  std::string& content_type,
261  std::string& content_header,
262  std::string& content_str,
263  dabc::Buffer& content_bin)
264 {
265 
266  std::string pathname, filename, query;
267 
268  content_header.clear();
269 
270  //ExtractPathAndFile(uri, pathname, filename);
271 
272  content_type = dabc::PublisherRef(GetPublisher()).UserInterfaceKind(uri, pathname, filename);
273 
274  DOUT2("http::Server::Process uri %s path %s file %s type %s", uri ? uri : "---", pathname.c_str(), filename.c_str(), content_type.c_str());
275 
276  if (content_type == "__error__") return false;
277 
278  if (content_type == "__user__") {
279  content_str = pathname;
280  if (filename.empty()) content_str += "main.htm";
281  else content_str += filename;
282  content_type = "__file__";
283  IsFileRequested(content_str.c_str(), content_str);
284  return true;
285  }
286 
287  if (filename == "draw.htm") {
288  content_str = fJsRootSys + "/files/draw.htm";
289  content_type = "__file__";
290  return true;
291  }
292 
293  // check that filename starts with some special prefix, in such case redirect it to other location
294 
295  if (filename.empty() || (filename=="main.htm") || (filename=="index.htm")) {
296  content_str = fJsRootSys + "/files/online.htm";
297  content_type = "__file__";
298  return true;
299  }
300 
301  for (unsigned n=0;n<fLocations.size();n++)
302  if (!fLocations[n].fNamePrefix.empty() && (filename.find(fLocations[n].fNamePrefix)==0)) {
303  size_t len = fLocations[n].fNamePrefix.length();
304  if ((filename.length()<=len) || (filename[len]=='.')) continue;
305  filename.erase(0, len);
306  if (!VerifyFilePath(filename.c_str())) return false;
307  content_str = fLocations[n].fFilePath + fLocations[n].fNamePrefixRepl + filename;
308  content_type = "__file__";
309  return true;
310  }
311 
312  if (_query!=0) query = _query;
313 
314  if (filename == "execute") {
315  if (pathname.empty()) return false;
316 
317  dabc::Command res = dabc::PublisherRef(GetPublisher()).ExeCmd(pathname, query);
318 
319  if (res.GetResult() <= 0) return false;
320 
321  content_type = "application/json";
322  content_str = res.SaveToJson();
323 
324  return true;
325  }
326 
327  if (filename.empty()) return false;
328 
329  bool iszipped = false;
330 
331  if ((filename.length() > 3) && (filename.rfind(".gz") == filename.length()-3)) {
332  filename.resize(filename.length()-3);
333  iszipped = true;
334  }
335 
336  if ((filename == "h.xml") || (filename == "h.json")) {
337 
338  bool isxml = (filename == "h.xml");
339 
340  dabc::CmdGetNamesList cmd(isxml ? "xml" : "json", pathname, query);
341 
342  if (!fAutoLoad.empty()) cmd.AddHeader("_autoload", fAutoLoad);
343  if (!fTopTitle.empty()) cmd.AddHeader("_toptitle", fTopTitle);
344  if (!fBrowser.empty()) cmd.AddHeader("_browser", fBrowser);
345  if (!fLayout.empty()) cmd.AddHeader("_layout", fLayout);
346  if (!fDrawItem.empty() && (pathname=="/")) {
347  cmd.AddHeader("_drawitem", fDrawItem);
348  cmd.AddHeader("_drawopt", fDrawOpt);
349  }
350  if (fMonitoring >= 100)
351  cmd.AddHeader("_monitoring", std::to_string(fMonitoring), false);
352 
353  dabc::WorkerRef wrk = GetPublisher();
354 
355  if (wrk.Execute(cmd) != dabc::cmd_true) return false;
356 
357  content_str = cmd.GetStr("astext");
358 
359  if (isxml) {
360  content_type = "text/xml";
361  content_str = std::string("<?xml version=\"1.0\"?>\n<dabc>\n") + content_str + "</dabc>";
362  } else {
363  content_type = "application/json";
364  }
365 
366  } else if (filename == "multiget.json") {
367 
368  content_type = "application/json";
369 
370  std::string opt = query;
372 
373  if (opt.find("items=")==0) {
374  std::size_t separ = opt.find("&");
375  if (separ == std::string::npos) separ = opt.length();
376  std::string items = opt.substr(6, separ-6);
377  opt = opt.erase(0, separ+1);
378  DOUT3("MULTIGET path %s items=%s rest:%s", pathname.c_str(), items.c_str(), opt.c_str());
379 
380  dabc::RecordField field(items);
381  std::vector<std::string> arr = field.AsStrVect();
382 
383  content_str="[";
384  dabc::WorkerRef ref = GetPublisher();
385 
386  for (unsigned n=0;n<arr.size();n++) {
387  if (n>0) content_str.append(",");
388 
389  dabc::CmdGetBinary cmd(pathname+arr[n], "get.json", opt);
390  cmd.SetTimeout(5.);
391 
392  if (ref.Execute(cmd) == dabc::cmd_true) {
393  content_bin = cmd.GetRawData();
394  }
395  content_str.append(dabc::format("{ \"item\": \"%s\", \"result\":", arr[n].c_str()));
396 
397  if (content_bin.null())
398  content_str.append("null");
399  else
400  content_str.append((const char*) content_bin.SegmentPtr(), content_bin.SegmentSize());
401 
402  content_str.append("}");
403 
404  content_bin.Release();
405  }
406 
407  content_str.append("]");
408 
409  } else {
410  content_str = "null";
411  }
412 
413  } else {
414 
415  dabc::CmdGetBinary cmd(pathname, filename, query);
416  cmd.SetTimeout(5.);
417 
418  dabc::WorkerRef ref = GetPublisher();
419 
420  if (ref.Execute(cmd) == dabc::cmd_true) {
421  content_type = cmd.GetStr("content_type");
422 
423  if (content_type.empty())
424  content_type = GetMimeType(filename.c_str());
425 
426  if (cmd.HasField("MVersion"))
427  content_header.append(dabc::format("MVersion: %u\r\n", cmd.GetUInt("MVersion")));
428 
429  if (cmd.HasField("BVersion"))
430  content_header.append(dabc::format("BVersion: %u\r\n", cmd.GetUInt("BVersion")));
431 
432  content_bin = cmd.GetRawData();
433  if (content_bin.null()) content_str = cmd.GetStr("StringReply");
434  }
435 
436  // TODO: in some cases empty binary may be not an error
437  if (content_bin.null() && (content_str.length()==0)) {
438  DOUT2("Is empty buffer is error for uri %s ?", uri);
439  return false;
440  }
441  }
442 
443  if (iszipped) {
444 #ifdef DABC_WITHOUT_ZLIB
445  DOUT0("It is requested to compress buffer, but ZLIB is not available!!!");
446 #else
447 
448  unsigned long objlen = 0;
449  Bytef* objptr = 0;
450 
451  if (!content_bin.null()) {
452  objlen = content_bin.GetTotalSize();
453  objptr = (Bytef*) content_bin.SegmentPtr();
454  } else {
455  objlen = content_str.length();
456  objptr = (Bytef*) content_str.c_str();
457  }
458 
459  unsigned long objcrc = crc32(0, NULL, 0);
460  objcrc = crc32(objcrc, objptr, objlen);
461 
462  // reserve place for header plus space required for the target buffer
463  unsigned long zipbuflen = 18 + compressBound(objlen);
464  if (zipbuflen<512) zipbuflen = 512;
465  void* zipbuf = malloc(zipbuflen);
466 
467  if (zipbuf==0) {
468  EOUT("Fail to allocate %lu bytes memory !!!", zipbuflen);
469  return true;
470  }
471 
472  char *bufcur = (char*) zipbuf;
473 
474  *bufcur++ = 0x1f; // first byte of ZIP identifier
475  *bufcur++ = 0x8b; // second byte of ZIP identifier
476  *bufcur++ = 0x08; // compression method
477  *bufcur++ = 0x00; // FLAG - empty, no any file names
478  *bufcur++ = 0; // empty timestamp
479  *bufcur++ = 0; //
480  *bufcur++ = 0; //
481  *bufcur++ = 0; //
482  *bufcur++ = 0; // XFL (eXtra FLags)
483  *bufcur++ = 3; // OS 3 means Unix
484 
485  zipbuflen-=18; // ZIP cannot use header and footer
486 
487  // WORKAROUD - seems to be, compress places 2 bytes before and 4 bytes after the compressed buffer
488 
489  // int res = compress((Bytef*)bufcur, &zipbuflen, objptr, objlen);
490  // bufcur += zipbuflen;
491 
492  int res = compress((Bytef*)bufcur-2, &zipbuflen, objptr, objlen);
493  if (res!=Z_OK) {
494  EOUT("Fail to compress buffer with ZLIB");
495  free(zipbuf);
496  return true;
497  }
498 
499  *(bufcur-2) = 0; // XFL (eXtra FLags)
500  *(bufcur-1) = 3; // OS 3 means Unix
501 
502  bufcur += (zipbuflen - 6);
503 
504 
505  *bufcur++ = objcrc & 0xff; // CRC32
506  *bufcur++ = (objcrc >> 8) & 0xff;
507  *bufcur++ = (objcrc >> 16) & 0xff;
508  *bufcur++ = (objcrc >> 24) & 0xff;
509 
510  *bufcur++ = objlen & 0xff; // original data length
511  *bufcur++ = (objlen >> 8) & 0xff;
512  *bufcur++ = (objlen >> 16) & 0xff;
513  *bufcur++ = (objlen >> 24) & 0xff;
514 
515  content_bin = dabc::Buffer::CreateBuffer(zipbuf, bufcur - (char*) zipbuf, true);
516  content_str.clear();
517 
518  content_header.append("Content-Encoding: gzip\r\n");
519 
520  DOUT3("Compress original object %lu into zip buffer %lu", objlen, zipbuflen);
521 #endif
522  }
523 
524  // exclude caching of dynamic data
525  content_header.append("Cache-Control: private, no-cache, no-store, must-revalidate, max-age=0, proxy-revalidate, s-maxage=0\r\n");
526 
527  return true;
528 }
Reference on memory from memory pool.
Definition: Buffer.h:135
unsigned SegmentSize(unsigned n=0) const
Returns size on the segment, no any boundary checks.
Definition: Buffer.h:174
BufferSize_t GetTotalSize() const
Return total size of all buffer segments.
Definition: Buffer.cxx:91
static Buffer CreateBuffer(BufferSize_t sz)
This static method create independent buffer for any other memory pools Therefore it can be used in s...
Definition: Buffer.cxx:419
void * SegmentPtr(unsigned n=0) const
Returns pointer on the segment, no any boundary checks.
Definition: Buffer.h:171
Command used to produce custom binary data for published in hierarchy entries.
Definition: Publisher.h:33
Command to request names list.
Definition: Publisher.h:58
void AddHeader(const std::string &name, const std::string &value, bool withquotes=true)
Definition: Publisher.h:69
Represents command with its arguments.
Definition: Command.h:99
unsigned GetUInt(const std::string &name, unsigned dflt=0) const
Definition: Command.h:148
std::string GetStr(const std::string &name, const std::string &dflt="") const
Definition: Command.h:136
Command & SetTimeout(double tm)
Set maximum time which can be used for command execution.
Definition: Command.cxx:108
int GetResult() const
Definition: Command.h:174
Buffer GetRawData()
Returns reference on raw data Can be called only once - raw data reference will be cleaned.
Definition: Command.cxx:347
int NeedAuth(const std::string &path)
Returns 1 - need auth, 0 - no need auth, -1 - undefined.
Definition: Publisher.cxx:960
Command ExeCmd(const std::string &fullname, const std::string &query)
Execute item is command, providing parameters in query.
Definition: Publisher.cxx:974
std::string UserInterfaceKind(const char *uri, std::string &path, std::string &fname)
Returns "" - undefined, "__tree__" – tree hierarchy "__single__" – single element "__file__" – just a...
Definition: Publisher.cxx:946
std::vector< std::string > AsStrVect() const
Definition: Record.cxx:923
std::string AsStr(const std::string &dflt="") const
Definition: Record.cxx:749
int64_t AsInt(int64_t dflt=0) const
Definition: Record.cxx:501
bool HasField(const std::string &name) const
Definition: Record.h:498
std::string SaveToJson(unsigned mask=0)
Store record in JSON form.
Definition: Record.cxx:1658
void Release()
Releases reference on the object.
Definition: Reference.cxx:138
bool null() const
Returns true if reference contains nullptr.
Definition: Reference.h:151
Uniform Resource Locator interpreter.
Definition: Url.h:33
std::string GetOptionStr(const std::string &optname, const std::string &dflt="") const
Definition: Url.cxx:281
bool HasOption(const std::string &optname) const
Definition: Url.h:70
static void ReplaceSpecialSymbols(std::string &opt)
!
Definition: Url.cxx:109
void SetOptions(const std::string &opt)
Method allows to set URL options directly to be able use all Get methods.
Definition: Url.cxx:102
Reference on dabc::Worker
Definition: Worker.h:466
bool Execute(Command cmd, double tmout=-1.)
Definition: Worker.cxx:1147
RecordField Cfg(const std::string &name, Command cmd=nullptr) const
Returns configuration field of specified name Configuration value of specified name searched in follo...
Definition: Worker.cxx:521
int fMonitoring
_monitoring value in h.json, only for top page
Definition: Server.h:53
bool IsFileRequested(const char *uri, std::string &fname)
Check if file is requested.
Definition: Server.cxx:207
static bool VerifyFilePath(const char *fname)
Check if relative path below current dir - prevents file access to top directories via http.
Definition: Server.cxx:169
std::string fJsRootSys
location of JSROOT code, need to read special files
Definition: Server.h:44
Server(const std::string &name, dabc::Command cmd=nullptr)
Definition: Server.cxx:99
std::string fHttpSys
location of http plugin, need to read special files
Definition: Server.h:43
std::string fDrawItem
_drawitem value in h.json, only for top page
Definition: Server.h:51
std::string fLayout
_layout value in h.json
Definition: Server.h:50
bool IsAuthRequired(const char *uri)
Returns true if authentication is required.
Definition: Server.cxx:240
std::string fTopTitle
_toptitle value in h.json
Definition: Server.h:48
std::string fBrowser
_browser value in h.json
Definition: Server.h:49
void ExtractPathAndFile(const char *uri, std::string &pathname, std::string &filename)
Definition: Server.cxx:226
virtual ~Server()
Definition: Server.cxx:152
std::string fDrawOpt
_drawopt value in h.json, only for top page
Definition: Server.h:52
bool Process(const char *uri, const char *query, std::string &content_type, std::string &content_header, std::string &content_str, dabc::Buffer &content_bin)
Method process different URL requests, should be called from server thread.
Definition: Server.cxx:259
std::string fAutoLoad
_autoload value in h.json
Definition: Server.h:47
static const char * GetMimeType(const char *fname)
Definition: Server.cxx:27
void AddLocation(const std::string &filepath, const std::string &absprefix, const std::string &nameprefix="", const std::string &nameprefixrepl="")
Add files location.
Definition: Server.cxx:156
#define DOUT2(args ...)
Definition: logging.h:170
#define DOUT0(args ...)
Definition: logging.h:156
#define DOUT3(args ...)
Definition: logging.h:176
#define EOUT(args ...)
Definition: logging.h:150
#define DOUT1(args ...)
Definition: logging.h:162
Event manipulation API.
Definition: api.h:23
std::string format(const char *fmt,...)
Definition: string.cxx:49
@ cmd_true
Definition: Command.h:38