1 // Gmsh - Copyright (C) 1997-2021 C. Geuzaine, J.-F. Remacle
2 //
3 // See the LICENSE.txt file in the Gmsh root directory for license information.
4 // Please report all issues on https://gitlab.onelab.info/gmsh/gmsh/issues.
5 
6 #include "GmshConfig.h"
7 #if !defined(HAVE_NO_STDINT_H)
8 #include <stdint.h>
9 #elif defined(HAVE_NO_INTPTR_T)
10 typedef unsigned long intptr_t;
11 #endif
12 #include "GmshGlobal.h"
13 #include "Context.h"
14 #include "OS.h"
15 #include "StringUtils.h"
16 #include "OpenFile.h"
17 #include "CreateFile.h"
18 #include "Options.h"
19 #include "GModel.h"
20 
21 #if defined(HAVE_ONELAB)
22 
23 #include "gmshLocalNetworkClient.h"
24 #include "onelabUtils.h"
25 
26 #if defined(HAVE_FLTK)
27 #include "FlGui.h"
28 #include "onelabGroup.h"
29 #include "drawContext.h"
30 #endif
31 
32 #if defined(HAVE_POST)
33 #include "PView.h"
34 #endif
35 
36 #if defined(HAVE_ONELAB_METAMODEL)
37 #include "OnelabClients.h"
38 #include "metamodel.h"
39 #endif
40 
41 class onelabGmshServer : public GmshServer {
42 private:
43   onelab::localNetworkClient *_client;
44 
45 public:
onelabGmshServer(onelab::localNetworkClient * client)46   onelabGmshServer(onelab::localNetworkClient *client)
47     : GmshServer(), _client(client)
48   {
49   }
~onelabGmshServer()50   ~onelabGmshServer() {}
NonBlockingSystemCall(const std::string & exe,const std::string & args)51   int NonBlockingSystemCall(const std::string &exe, const std::string &args)
52   {
53     return SystemCallExe(exe, args);
54   }
NonBlockingWait(double waitint,double timeout,int socket)55   int NonBlockingWait(double waitint, double timeout, int socket)
56   {
57     double start = TimeOfDay();
58     while(1) {
59       if(timeout > 0 && TimeOfDay() - start > timeout)
60         return 2; // timeout
61       if(_client->getPid() < 0 ||
62          (_client->getExecutable().empty() && !CTX::instance()->solver.listen))
63         return 1; // process has been killed or we stopped listening
64       int ret = 0;
65 #if defined(HAVE_FLTK)
66       if(FlGui::available()) {
67         // if GUI available, check if there is data and return immediately (we
68         // will wait for GUI events later - see below)
69         ret = Select(0, 0, socket);
70       }
71       else {
72         ret = Select(0, waitint * 1e6, socket);
73       }
74 #else
75       ret = Select(0, waitint * 1e6, socket);
76 #endif
77       if(ret == 0) { // nothing available
78 #if defined(HAVE_FLTK)
79         if(FlGui::available()) {
80           if(timeout < 0) {
81             // if asked, refresh the onelab GUI, but no more than every 1/4th of
82             // a second
83             static double lastRefresh = 0.;
84             if(start - lastRefresh > 0.25) {
85               std::vector<onelab::string> ps;
86               onelab::server::instance()->get(ps, "Gmsh/Action");
87               if(ps.size() && ps[0].getValue() == "refresh") {
88                 ps[0].setVisible(false);
89                 ps[0].setValue("");
90                 onelab::server::instance()->set(ps[0]);
91                 if(FlGui::available()) onelab_cb(nullptr, (void *)"refresh");
92               }
93               lastRefresh = start;
94             }
95           }
96           // wait at most waitint seconds and respond to FLTK events if ready
97           if(Fl::ready()) FlGui::wait(waitint);
98         }
99 #endif
100         // return to caller (we will be back here soon again)
101         if(timeout < 0) return 3;
102       }
103       else if(ret > 0) {
104         return 0; // data is there!
105       }
106       else {
107         // an error happened
108         _client->setPid(-1);
109         return 1;
110       }
111     }
112   }
LaunchClient()113   int LaunchClient()
114   {
115     std::string sockname;
116     std::ostringstream tmp;
117     static bool first = true;
118     const char *port = strstr(CTX::instance()->solver.socketName.c_str(), ":");
119     if(!port) {
120       // Unix socket
121       tmp << CTX::instance()->homeDir << CTX::instance()->solver.socketName
122           << _client->getId();
123       sockname = FixWindowsPath(tmp.str());
124     }
125     else {
126       // TCP/IP socket
127       if(CTX::instance()->solver.socketName.size() &&
128          CTX::instance()->solver.socketName[0] == ':')
129         tmp << GetHostName(); // prepend hostname if only port number is given
130       tmp << CTX::instance()->solver.socketName;
131       if(atoi(port + 1)) {
132         // if a port number is given we use it for the first client, then we
133         // append the client id so that we can manage several clients at once;
134         // this is ugly but it leads to the expected result if a single client
135         // is instanciated (in general it is recommended to *not* specify the
136         // port number, and let the OS assign a port number automatically)
137         if(!first) {
138           tmp << _client->getId();
139           first = false;
140         }
141       }
142       sockname = tmp.str();
143     }
144 
145     std::string exe = FixWindowsPath(_client->getExecutable());
146     std::string args;
147     if(exe.size()) {
148       if(_client->treatExecutableAsFullCommandLine()) {
149         args = exe;
150         exe = "";
151       }
152       else {
153         std::vector<std::string> cl = onelabUtils::getCommandLine(_client);
154         for(std::size_t i = 0; i < cl.size(); i++) args.append(" " + cl[i]);
155       }
156       args.append(" " + _client->getSocketSwitch() + " \"" +
157                   _client->getName() + "\" %s");
158     }
159     else {
160       Msg::Info("Listening on socket '%s'", sockname.c_str());
161     }
162 
163     int sock;
164     try {
165       sock = Start(exe, args, sockname, CTX::instance()->solver.timeout);
166     } catch(const char *err) {
167       Msg::Error("Abnormal server termination (%s on socket %s)", err,
168                  sockname.c_str());
169       sock = -1;
170     }
171 
172     return sock;
173   }
174 };
175 
tryToGetGmshNumberOption(const std::string & name)176 static std::string tryToGetGmshNumberOption(const std::string &name)
177 {
178   std::string reply;
179   std::string::size_type dot = name.find('.');
180   if(dot != std::string::npos) {
181     double val;
182     if(GmshGetOption(name.substr(0, dot), name.substr(dot + 1), val)) {
183       onelab::number par(name, val);
184       reply = par.toChar();
185     }
186   }
187   return reply;
188 }
189 
tryToGetGmshStringOption(const std::string & name)190 static std::string tryToGetGmshStringOption(const std::string &name)
191 {
192   std::string reply;
193   std::string::size_type dot = name.find('.');
194   if(dot != std::string::npos) {
195     std::string val;
196     if(GmshGetOption(name.substr(0, dot), name.substr(dot + 1), val)) {
197       onelab::string par(name, val);
198       reply = par.toChar();
199     }
200   }
201   return reply;
202 }
203 
tryToSetGmshNumberOption(onelab::number & p)204 static bool tryToSetGmshNumberOption(onelab::number &p)
205 {
206   const std::string &name = p.getName();
207   std::string::size_type dot = name.find('.');
208   if(dot != std::string::npos) {
209     double val = p.getValue();
210     return GmshSetOption(name.substr(0, dot), name.substr(dot + 1), val);
211   }
212   return false;
213 }
214 
tryToSetGmshStringOption(onelab::string & p)215 static bool tryToSetGmshStringOption(onelab::string &p)
216 {
217   const std::string &name = p.getName();
218   std::string::size_type dot = name.find('.');
219   if(dot != std::string::npos) {
220     const std::string &val = p.getValue();
221     return GmshSetOption(name.substr(0, dot), name.substr(dot + 1), val);
222   }
223   return false;
224 }
225 
receiveMessage(gmshLocalNetworkClient * master)226 bool gmshLocalNetworkClient::receiveMessage(gmshLocalNetworkClient *master)
227 {
228   // receive a message on the associated GmshServer; 'master' is only used when
229   // creating subclients with GMSH_CONNECT.
230 
231   double timer = TimeOfDay();
232 
233   if(!getGmshServer()) {
234     Msg::Error("Abnormal server termination (no valid server)");
235     return false;
236   }
237 
238   int type, length, swap;
239   if(!getGmshServer()->ReceiveHeader(&type, &length, &swap)) {
240     Msg::Error("Abnormal server termination (did not receive message header)");
241     return false;
242   }
243 
244   std::string message(length, ' '), blank = message;
245   if(!getGmshServer()->ReceiveMessage(length, &message[0])) {
246     Msg::Error("Abnormal server termination (did not receive message body)");
247     return false;
248   }
249 
250   if(message == blank &&
251      !(type == GmshSocket::GMSH_PROGRESS || type == GmshSocket::GMSH_INFO ||
252        type == GmshSocket::GMSH_WARNING || type == GmshSocket::GMSH_ERROR)) {
253     // we should still allow blank msg strings to be sent
254     Msg::Error(
255       "Abnormal server termination (blank message: client not stopped?)");
256     return false;
257   }
258 
259   switch(type) {
260   case GmshSocket::GMSH_START: setPid(atoi(message.c_str())); break;
261   case GmshSocket::GMSH_STOP:
262     setPid(-1);
263     if(getFather()) {
264       std::string reply = getName(); // reply is dummy
265       getFather()->getGmshServer()->SendMessage(GmshSocket::GMSH_STOP,
266                                                 (int)reply.size(), &reply[0]);
267     }
268     break;
269   case GmshSocket::GMSH_PARAMETER:
270   case GmshSocket::GMSH_PARAMETER_WITHOUT_CHOICES:
271   case GmshSocket::GMSH_PARAMETER_UPDATE: {
272     std::string version, ptype, name;
273     onelab::parameter::getInfoFromChar(message, version, ptype, name);
274     if(onelab::parameter::version() != version) {
275       Msg::Error("ONELAB version mismatch (server: %s / client: %s)",
276                  onelab::parameter::version().c_str(), version.c_str());
277     }
278     else if(ptype == "number") {
279       onelab::number p;
280       p.fromChar(message);
281       if(!tryToSetGmshNumberOption(p)) {
282         if(type == GmshSocket::GMSH_PARAMETER_WITHOUT_CHOICES) {
283           // append value to any choices already on the server
284           std::vector<onelab::number> par;
285           get(par, name);
286           std::vector<double> c;
287           if(par.size()) c = par[0].getChoices();
288           c.push_back(p.getValue());
289           p.setChoices(c);
290         }
291         if(type == GmshSocket::GMSH_PARAMETER_UPDATE) {
292           std::vector<onelab::number> par;
293           get(par, name);
294           if(par.size()) {
295             onelab::number y = p;
296             p = par[0];
297             onelabUtils::updateNumber(p, y);
298           }
299         }
300         set(p);
301         if(p.getName() == getName() + "/Progress") {
302 #if defined(HAVE_FLTK)
303           if(FlGui::available())
304             FlGui::instance()->setProgress(p.getLabel().c_str(), p.getValue(),
305                                            p.getMin(), p.getMax());
306 #endif
307         }
308       }
309     }
310     else if(ptype == "string") {
311       onelab::string p;
312       p.fromChar(message);
313       if(!tryToSetGmshStringOption(p)) {
314         if(type == GmshSocket::GMSH_PARAMETER_WITHOUT_CHOICES) {
315           // append value to any choices already on the server
316           std::vector<onelab::string> par;
317           get(par, name);
318           std::vector<std::string> c;
319           if(par.size()) c = par[0].getChoices();
320           c.push_back(p.getValue());
321           p.setChoices(c);
322         }
323         else if(type == GmshSocket::GMSH_PARAMETER_UPDATE) {
324           std::vector<onelab::string> par;
325           get(par, name);
326           if(par.size()) {
327             onelab::string y = p;
328             p = par[0];
329             onelabUtils::updateString(p, y);
330           }
331         }
332         set(p);
333       }
334     }
335     else
336       Msg::Error("Unknown ONELAB parameter type: %s", ptype.c_str());
337   } break;
338   case GmshSocket::GMSH_PARAMETER_QUERY:
339   case GmshSocket::GMSH_PARAMETER_QUERY_WITHOUT_CHOICES: {
340     std::string version, ptype, name, reply;
341     onelab::parameter::getInfoFromChar(message, version, ptype, name);
342     if(onelab::parameter::version() != version) {
343       Msg::Error("ONELAB version mismatch (server: %s / client: %s)",
344                  onelab::parameter::version().c_str(), version.c_str());
345     }
346     else if(ptype == "number") {
347       std::vector<onelab::number> par;
348       if(type == GmshSocket::GMSH_PARAMETER_QUERY_WITHOUT_CHOICES)
349         getWithoutChoices(par, name);
350       else
351         get(par, name);
352       if(par.empty())
353         reply = tryToGetGmshNumberOption(name);
354       else
355         reply = par[0].toChar();
356     }
357     else if(ptype == "string") {
358       std::vector<onelab::string> par;
359       if(type == GmshSocket::GMSH_PARAMETER_QUERY_WITHOUT_CHOICES)
360         getWithoutChoices(par, name);
361       else
362         get(par, name);
363       if(par.empty())
364         reply = tryToGetGmshStringOption(name);
365       else
366         reply = par[0].toChar();
367     }
368     else
369       Msg::Error("Unknown ONELAB parameter type in query: %s", ptype.c_str());
370 
371     if(reply.size()) {
372       getGmshServer()->SendMessage(GmshSocket::GMSH_PARAMETER,
373                                    (int)reply.size(), &reply[0]);
374     }
375     else {
376       reply = name;
377       getGmshServer()->SendMessage(GmshSocket::GMSH_PARAMETER_NOT_FOUND,
378                                    (int)reply.size(), &reply[0]);
379     }
380   } break;
381   case GmshSocket::GMSH_PARAMETER_QUERY_ALL: {
382     std::string version, ptype, name, reply;
383     std::vector<std::string> replies;
384     onelab::parameter::getInfoFromChar(message, version, ptype, name);
385     if(onelab::parameter::version() != version) {
386       Msg::Error("ONELAB version mismatch (server: %s / client: %s)",
387                  onelab::parameter::version().c_str(), version.c_str());
388     }
389     else if(ptype == "number") {
390       std::vector<onelab::number> numbers;
391       get(numbers);
392       for(auto it = numbers.begin();
393           it != numbers.end(); it++)
394         replies.push_back((*it).toChar());
395     }
396     else if(ptype == "string") {
397       std::vector<onelab::string> strings;
398       get(strings);
399       for(auto it = strings.begin();
400           it != strings.end(); it++)
401         replies.push_back((*it).toChar());
402     }
403     else
404       Msg::Error("Unknown ONELAB parameter type in query: %s", ptype.c_str());
405 
406     for(std::size_t i = 0; i < replies.size(); i++)
407       getGmshServer()->SendMessage(GmshSocket::GMSH_PARAMETER_QUERY_ALL,
408                                    (int)replies[i].size(), &replies[i][0]);
409     reply = "Sent all ONELAB " + ptype + "s";
410     getGmshServer()->SendMessage(GmshSocket::GMSH_PARAMETER_QUERY_END,
411                                  (int)reply.size(), &reply[0]);
412   } break;
413   case GmshSocket::GMSH_PARAMETER_CLEAR:
414     clear(message == "*" ? "" : message);
415     break;
416   case GmshSocket::GMSH_PROGRESS:
417     Msg::StatusBar(false, "%s %s", _name.c_str(), message.c_str());
418     break;
419   case GmshSocket::GMSH_INFO:
420     Msg::Direct("Info    : %s - %s", _name.c_str(), message.c_str());
421     break;
422   case GmshSocket::GMSH_WARNING:
423     Msg::Warning("%s - %s", _name.c_str(), message.c_str());
424     break;
425   case GmshSocket::GMSH_ERROR:
426     Msg::Error("%s - %s", _name.c_str(), message.c_str());
427     break;
428   case GmshSocket::GMSH_MERGE_FILE:
429     if(CTX::instance()->solver.autoMergeFile) {
430 #if defined(HAVE_FLTK)
431       std::size_t n = PView::list.size();
432 #endif
433       bool changedBeforeMerge = onelab::server::instance()->getChanged("Gmsh");
434       MergePostProcessingFile(message, CTX::instance()->solver.autoShowViews,
435                               CTX::instance()->solver.autoShowLastStep, true);
436       onelab::server::instance()->setChanged(changedBeforeMerge, "Gmsh");
437 #if defined(HAVE_FLTK)
438       drawContext::global()->draw();
439       if(FlGui::available() && n != PView::list.size()) {
440         FlGui::instance()->rebuildTree(true);
441         FlGui::instance()->openModule("Post-processing");
442       }
443 #endif
444     }
445     break;
446   case GmshSocket::GMSH_OPEN_PROJECT: OpenProject(message);
447 #if defined(HAVE_FLTK)
448     drawContext::global()->draw();
449 #endif
450     break;
451   case GmshSocket::GMSH_PARSE_STRING: ParseString(message, true);
452 #if defined(HAVE_FLTK)
453     drawContext::global()->draw();
454 #endif
455     break;
456   case GmshSocket::GMSH_SPEED_TEST:
457     Msg::Info("Got %d Mb message in %g seconds", length / 1024 / 1024,
458               TimeOfDay() - timer);
459     break;
460   case GmshSocket::GMSH_VERTEX_ARRAY: {
461 #if defined(HAVE_FLTK)
462     int n = PView::list.size();
463 #endif
464 #if defined(HAVE_POST)
465     PView::fillVertexArray(this, length, &message[0], swap);
466 #endif
467 #if defined(HAVE_FLTK)
468     if(FlGui::available())
469       FlGui::instance()->updateViews(n != (int)PView::list.size(), true);
470     drawContext::global()->draw();
471 #endif
472   } break;
473   case GmshSocket::GMSH_CONNECT: {
474     std::string::size_type first = 0;
475     std::string clientName = onelab::parameter::getNextToken(message, first);
476     std::string command = onelab::parameter::getNextToken(message, first);
477     gmshLocalNetworkClient *subClient =
478       new gmshLocalNetworkClient(clientName, command, "", true);
479     onelabGmshServer *server = new onelabGmshServer(subClient);
480     subClient->setPid(0);
481     onelab::string o(subClient->getName() + "/Action", "compute");
482     o.setVisible(false);
483     o.setNeverChanged(true);
484     onelab::server::instance()->set(o);
485     int sock = server->LaunchClient();
486     if(sock < 0) { // could not establish the connection: aborting
487       server->Shutdown();
488       delete server;
489       Msg::Error("Could not connect client '%s'", subClient->getName().c_str());
490     }
491     else {
492       Msg::StatusBar(true, "Running '%s'...", subClient->getName().c_str());
493       subClient->setGmshServer(server);
494       subClient->setFather(this);
495       master->addClient(subClient);
496     }
497   } break;
498   case GmshSocket::GMSH_OLPARSE: {
499     std::string reply = "unavailable";
500 #if defined(HAVE_ONELAB_METAMODEL)
501     std::string::size_type first = 0;
502     std::string clientName = onelab::parameter::getNextToken(message, first);
503     std::string fullName = onelab::parameter::getNextToken(message, first);
504     preProcess(clientName, fullName); // contrib/onelab/OnelabParser.cpp
505     Msg::Info("Done preprocessing file '%s'", fullName.c_str());
506     reply =
507       onelab::server::instance()->getChanged(clientName) ? "true" : "false";
508 #endif
509     getGmshServer()->SendMessage(GmshSocket::GMSH_OLPARSE,
510                                  (int)reply.size(), &reply[0]);
511   } break;
512   case GmshSocket::GMSH_CLIENT_CHANGED: {
513     std::string::size_type first = 0;
514     std::string command = onelab::parameter::getNextToken(message, first);
515     std::string name = onelab::parameter::getNextToken(message, first);
516     if(command == "get") {
517       std::string reply =
518         onelab::server::instance()->getChanged(name) ? "true" : "false";
519       getGmshServer()->SendMessage(GmshSocket::GMSH_CLIENT_CHANGED,
520                                    (int)reply.size(), &reply[0]);
521     }
522     else if(command == "set") {
523       std::string changed = onelab::parameter::getNextToken(message, first);
524       onelab::server::instance()->setChanged(changed == "true" ? 31 : 0, name);
525     }
526   } break;
527   default:
528     Msg::Warning("Received unknown message type (%d)", type);
529     return false;
530     break;
531   }
532 
533   return true;
534 }
535 
run()536 bool gmshLocalNetworkClient::run()
537 {
538 new_connection:
539   setPid(0); // dummy pid, should be non-negative
540 
541   onelabGmshServer *server = new onelabGmshServer(this);
542 
543   int sock = server->LaunchClient();
544 
545   if(sock < 0) {
546     // could not establish the connection: aborting
547     server->Shutdown();
548     delete server;
549     return false;
550   }
551 
552   Msg::StatusBar(true, "Running '%s'...", _name.c_str());
553 
554   setGmshServer(server);
555 
556   while(1) {
557     if(getExecutable().empty() && !CTX::instance()->solver.listen) {
558       // we stopped listening to the special "Listen" client
559       break;
560     }
561 
562     // loop over all the clients (usually only one, but can be more if we
563     // spawned subclients) and check if data is available for one of them
564     bool stop = false, haveData = false;
565     gmshLocalNetworkClient *c = nullptr;
566     std::vector<gmshLocalNetworkClient *> toDelete;
567     for(int i = 0; i < getNumClients(); i++) {
568       c = getClient(i);
569       if(c->getPid() < 0) {
570         if(c == this) { // the "master" client stopped
571           stop = true;
572           break;
573         }
574         else {
575           // this subclient is not active anymore: shut down and delete its
576           // server and mark the client for deletion
577           GmshServer *s = c->getGmshServer();
578           c->setGmshServer(nullptr);
579           c->setFather(nullptr);
580           if(s) {
581             s->Shutdown();
582             delete s;
583           }
584           toDelete.push_back(c);
585           continue;
586         }
587       }
588       GmshServer *s = c->getGmshServer();
589       if(!s) {
590         Msg::Error("Abnormal server termination (no valid server)");
591         stop = true;
592         break;
593       }
594       else {
595         int ret = s->NonBlockingWait(0.0001, -1.);
596         if(ret == 0) { // we have data from this particular client
597           haveData = true;
598           break;
599         }
600         else if(ret == 3) { // pass to the next client
601           continue;
602         }
603         else { // an error occurred
604           stop = true;
605           break;
606         }
607       }
608     }
609     for(std::size_t i = 0; i < toDelete.size(); i++) {
610       removeClient(toDelete[i]);
611       delete toDelete[i];
612     }
613 
614     // break the while(1) if the master client has stopped or if we encountered
615     // a problem
616     if(stop) break;
617 
618     // if data is available try to get the message from the corresponding
619     // client; break the while(1) if we could not receive the message
620     if(haveData && !c->receiveMessage(this)) break;
621 
622     // break the while(1) if the master client has stopped
623     if(c == this && c->getPid() < 0) break;
624   }
625 
626   // we are done running the (master) client: delete the servers and the
627   // subclients, if any remain (they should have been deleted already).
628   std::vector<gmshLocalNetworkClient *> toDelete;
629   for(int i = 0; i < getNumClients(); i++) {
630     gmshLocalNetworkClient *c = getClient(i);
631     GmshServer *s = c->getGmshServer();
632     c->setGmshServer(nullptr);
633     c->setFather(nullptr);
634     if(s) {
635       s->Shutdown();
636       delete s;
637     }
638     if(c != this) {
639       if(c->getPid() > 0)
640         Msg::Error("Subclient %s was not stopped correctly",
641                    c->getName().c_str());
642       toDelete.push_back(c);
643     }
644   }
645   for(std::size_t i = 0; i < toDelete.size(); i++) {
646     removeClient(toDelete[i]);
647     delete toDelete[i];
648   }
649 
650   Msg::StatusBar(true, "Done running '%s'", _name.c_str());
651 
652   if(getExecutable().empty()) {
653     Msg::Info("Client disconnected: starting new connection");
654     goto new_connection;
655   }
656 
657   return true;
658 }
659 
kill()660 bool gmshLocalNetworkClient::kill()
661 {
662   // FIXME: we should kill all the clients in the list
663   if(getPid() > 0) {
664     if(KillProcess(getPid())) {
665       Msg::Info("Killed '%s' (pid %d)", _name.c_str(), getPid());
666 #if defined(HAVE_FLTK)
667       if(FlGui::available()) FlGui::instance()->setProgress("Killed", 0, 0, 0);
668 #endif
669       setPid(-1);
670       return true;
671     }
672   }
673   setPid(-1);
674   return false;
675 }
676 
677 #endif
678