1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2009-2018, The GROMACS development team.
5  * Copyright (c) 2019,2021, by the GROMACS development team, led by
6  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7  * and including many others, as listed in the AUTHORS file in the
8  * top-level source directory and at http://www.gromacs.org.
9  *
10  * GROMACS is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public License
12  * as published by the Free Software Foundation; either version 2.1
13  * of the License, or (at your option) any later version.
14  *
15  * GROMACS is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with GROMACS; if not, see
22  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
24  *
25  * If you want to redistribute modifications to GROMACS, please
26  * consider that scientific software is very special. Version
27  * control is crucial - bugs must be traceable. We will be happy to
28  * consider code for inclusion in the official distribution, but
29  * derived work must not be called official GROMACS. Details are found
30  * in the README & COPYING files - if they are missing, get the
31  * official version at http://www.gromacs.org.
32  *
33  * To help us fund GROMACS development, we humbly ask that you cite
34  * the research papers on the package. Check out http://www.gromacs.org.
35  */
36 #include "gmxpre.h"
37 
38 #include "vmdio.h"
39 
40 #include "config.h"
41 
42 #include "gromacs/utility/path.h"
43 #include "gromacs/utility/stringutil.h"
44 
45 /* Derived from PluginMgr.C and catdcd.c */
46 
47 /* PluginMgr.C: Copyright: */
48 /***************************************************************************
49  * cr
50  * cr            (C) Copyright 1995-2009 The Board of Trustees of the
51  * cr                        University of Illinois
52  * cr                         All Rights Reserved
53  * cr
54    Developed by:           Theoretical and Computational Biophysics Group
55                         University of Illinois at Urbana-Champaign
56                         http://www.ks.uiuc.edu/
57 
58    Permission is hereby granted, free of charge, to any person obtaining a copy of
59    this software and associated documentation files (the Software), to deal with
60    the Software without restriction, including without limitation the rights to
61    use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
62    of the Software, and to permit persons to whom the Software is furnished to
63    do so, subject to the following conditions:
64 
65    Redistributions of source code must retain the above copyright notice,
66    this list of conditions and the following disclaimers.
67 
68    Redistributions in binary form must reproduce the above copyright notice,
69    this list of conditions and the following disclaimers in the documentation
70    and/or other materials provided with the distribution.
71 
72    Neither the names of Theoretical and Computational Biophysics Group,
73    University of Illinois at Urbana-Champaign, nor the names of its contributors
74    may be used to endorse or promote products derived from this Software without
75    specific prior written permission.
76 
77    THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
78    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
79    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
80    THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
81    OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
82    ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
83    OTHER DEALINGS WITH THE SOFTWARE.
84  ***************************************************************************/
85 
86 /* catdcd.c: Copyright: */
87 /*****************************************************************************/
88 /*                                                                           */
89 /* (C) Copyright 2001-2005 Justin Gullingsrud and the University of Illinois.*/
90 /*                                                                           */
91 /*****************************************************************************/
92 
93 #include <cassert>
94 #include <cstdio>
95 #include <cstdlib>
96 #include <cstring>
97 
98 /*
99  * Plugin header files; get plugin source from www.ks.uiuc.edu/Research/vmd"
100  */
101 #include "external/vmd_molfile/molfile_plugin.h"
102 #include "external/vmd_molfile/vmddlopen.h"
103 #if !GMX_NATIVE_WINDOWS
104 #    include <glob.h>
105 #else
106 #    ifndef _WIN32_IE
107 #        define _WIN32_IE 0x0500 /* SHGetFolderPath is available since WinXP/IE5 */
108 #    endif
109 #    include <shlobj.h>
110 #    include <windows.h>
111 #endif
112 
113 #include "gromacs/fileio/gmxfio.h"
114 #include "gromacs/math/vec.h"
115 #include "gromacs/pbcutil/pbc.h"
116 #include "gromacs/trajectory/trajectoryframe.h"
117 #include "gromacs/utility/basedefinitions.h"
118 #include "gromacs/utility/fatalerror.h"
119 #include "gromacs/utility/futil.h"
120 #include "gromacs/utility/smalloc.h"
121 
122 
123 typedef int (*initfunc)();
124 typedef int (*regfunc)(void*, vmdplugin_register_cb);
125 typedef int (*finifunc)();
126 
127 
register_cb(void * v,vmdplugin_t * p)128 static int register_cb(void* v, vmdplugin_t* p)
129 {
130     const char*      key       = p->name;
131     gmx_vmdplugin_t* vmdplugin = static_cast<gmx_vmdplugin_t*>(v);
132 
133     if (strcmp(key, vmdplugin->filetype) == 0)
134     {
135         vmdplugin->api = reinterpret_cast<molfile_plugin_t*>(p);
136     }
137     return VMDPLUGIN_SUCCESS;
138 }
139 
load_sharedlibrary_plugins(const char * fullpath,gmx_vmdplugin_t * vmdplugin)140 static int load_sharedlibrary_plugins(const char* fullpath, gmx_vmdplugin_t* vmdplugin)
141 {
142     /* Open the dll; try to execute the init function. */
143     void *handle, *ifunc, *registerfunc;
144     handle = vmddlopen(fullpath);
145     if (!handle)
146     {
147         if (debug)
148         {
149             fprintf(debug, "\nUnable to open dynamic library %s.\n%s\n", fullpath,
150                     vmddlerror()); /*only to debug because of stdc++ erros */
151         }
152         return 0;
153     }
154 
155     ifunc = vmddlsym(handle, "vmdplugin_init");
156     if (!ifunc || (reinterpret_cast<initfunc>(ifunc))())
157     {
158         printf("\nvmdplugin_init() for %s returned an error; plugin(s) not loaded.\n", fullpath);
159         vmddlclose(handle);
160         return 0;
161     }
162 
163     registerfunc = vmddlsym(handle, "vmdplugin_register");
164     if (!registerfunc)
165     {
166         printf("\nDidn't find the register function in %s; plugin(s) not loaded.\n", fullpath);
167         vmddlclose(handle);
168         return 0;
169     }
170     else
171     {
172         /* Load plugins from the library.*/
173         (reinterpret_cast<regfunc>(registerfunc))(vmdplugin, register_cb);
174     }
175 
176     /* in case this library does not support the filetype, close it */
177     if (vmdplugin->api == nullptr)
178     {
179         vmddlclose(handle);
180     }
181 
182     return 1;
183 }
184 
185 /*return: 1: success, 0: last frame, -1: error*/
read_next_vmd_frame(gmx_vmdplugin_t * vmdplugin,t_trxframe * fr)186 gmx_bool read_next_vmd_frame(gmx_vmdplugin_t* vmdplugin, t_trxframe* fr)
187 {
188     int                rc, i;
189     rvec               vec, angle;
190     molfile_timestep_t ts;
191 
192 
193     fr->bV = vmdplugin->bV;
194 
195 #if GMX_DOUBLE
196     snew(ts.coords, fr->natoms * 3);
197     if (fr->bV)
198     {
199         snew(ts.velocities, fr->natoms * 3);
200     }
201 #else
202     ts.coords = reinterpret_cast<float*>(fr->x);
203     if (fr->bV)
204     {
205         ts.velocities = reinterpret_cast<float*>(fr->v);
206     }
207 #endif
208 
209     rc = vmdplugin->api->read_next_timestep(vmdplugin->handle, fr->natoms, &ts);
210 
211     if (rc < -1)
212     {
213         fprintf(stderr, "\nError reading input file (error code %d)\n", rc);
214     }
215     if (rc < 0)
216     {
217         vmdplugin->api->close_file_read(vmdplugin->handle);
218         return false;
219     }
220 
221 #if GMX_DOUBLE
222     for (i = 0; i < fr->natoms; i++)
223     {
224         fr->x[i][0] = .1 * ts.coords[i * 3];
225         fr->x[i][1] = .1 * ts.coords[i * 3 + 1];
226         fr->x[i][2] = .1 * ts.coords[i * 3 + 2];
227         if (fr->bV)
228         {
229             fr->v[i][0] = .1 * ts.velocities[i * 3];
230             fr->v[i][1] = .1 * ts.velocities[i * 3 + 1];
231             fr->v[i][2] = .1 * ts.velocities[i * 3 + 2];
232         }
233     }
234     sfree(ts.coords);
235     if (fr->bV)
236     {
237         sfree(ts.velocities);
238     }
239 #else
240     for (i = 0; i < fr->natoms; i++)
241     {
242         svmul(.1, fr->x[i], fr->x[i]);
243         if (fr->bV)
244         {
245             svmul(.1, fr->v[i], fr->v[i]);
246         }
247     }
248 #endif
249 
250     fr->bX   = true;
251     fr->bBox = true;
252     vec[0]   = .1 * ts.A;
253     vec[1]   = .1 * ts.B;
254     vec[2]   = .1 * ts.C;
255     angle[0] = ts.alpha;
256     angle[1] = ts.beta;
257     angle[2] = ts.gamma;
258     matrix_convert(fr->box, vec, angle);
259     if (vmdplugin->api->abiversion > 10)
260     {
261         fr->bTime = TRUE;
262         fr->time  = ts.physical_time;
263     }
264     else
265     {
266         fr->bTime = FALSE;
267     }
268 
269 
270     return true;
271 }
272 
load_vmd_library(const char * fn,gmx_vmdplugin_t * vmdplugin)273 static int load_vmd_library(const char* fn, gmx_vmdplugin_t* vmdplugin)
274 {
275     const char* err;
276     int         ret = 0;
277 #if !GMX_NATIVE_WINDOWS
278     glob_t            globbuf;
279     const std::string defpath_suffix = "/plugins/*/molfile";
280     const std::string defpathenv     = GMX_VMD_PLUGIN_PATH;
281 #else
282     WIN32_FIND_DATA ffd;
283     HANDLE          hFind = INVALID_HANDLE_VALUE;
284     char            progfolder[GMX_PATH_MAX];
285     std::string     defpath_suffix = "\\plugins\\WIN32\\molfile";
286     SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, progfolder);
287     std::string defpathenv =
288             gmx::formatString("%s\\University of Illinois\\VMD\\plugins\\WIN32\\molfile", progfolder);
289 #endif
290 
291     vmdplugin->api      = nullptr;
292     vmdplugin->filetype = strrchr(fn, '.');
293     if (!vmdplugin->filetype)
294     {
295         return 0;
296     }
297     vmdplugin->filetype++;
298 
299     /* First look for an explicit path given at run time for the
300      * plugins, then an implicit run-time path, and finally for one
301      * given at configure time. This last might be hard-coded to the
302      * default for VMD installs. */
303     const char* pathenv = getenv("VMD_PLUGIN_PATH");
304     std::string fallBackPathEnv;
305     if (!pathenv)
306     {
307         pathenv = getenv("VMDDIR");
308         if (!pathenv)
309         {
310             printf("\nNeither VMD_PLUGIN_PATH or VMDDIR set. ");
311             printf("Using default location:\n%s\n", defpathenv.c_str());
312             pathenv = defpathenv.c_str();
313         }
314         else
315         {
316             printf("\nVMD_PLUGIN_PATH no set, but VMDDIR is set. ");
317             fallBackPathEnv = gmx::Path::join(pathenv, defpath_suffix);
318             pathenv         = fallBackPathEnv.c_str();
319             printf("Using semi-default location:\n%s\n", pathenv);
320         }
321     }
322 #if !GMX_NATIVE_WINDOWS
323     std::string pathname = gmx::Path::join(pathenv, "/*.so");
324     glob(pathname.c_str(), 0, nullptr, &globbuf);
325     if (globbuf.gl_pathc == 0)
326     {
327         printf("\nNo VMD Plugins found\n"
328                "Set the environment variable VMD_PLUGIN_PATH to the molfile folder within the\n"
329                "VMD installation.\n"
330                "The architecture (e.g. 32bit versus 64bit) of GROMACS and VMD has to match.\n");
331         return 0;
332     }
333     for (size_t i = 0; i < globbuf.gl_pathc && vmdplugin->api == nullptr; i++)
334     {
335         /* FIXME: Undefined which plugin is chosen if more than one plugin
336            can read a certain file ending. Requires some additional command
337            line option or enviroment variable to specify which plugin should
338            be picked.
339          */
340         ret |= load_sharedlibrary_plugins(globbuf.gl_pathv[i], vmdplugin);
341     }
342     globfree(&globbuf);
343 #else
344     std::string pathname = gmx::Path::join(pathenv, "\\*.so");
345     hFind                = FindFirstFile(pathname.c_str(), &ffd);
346     if (INVALID_HANDLE_VALUE == hFind)
347     {
348         printf("\nNo VMD Plugins found\n");
349         return 0;
350     }
351     do
352     {
353         std::string filename = gmx::Path::join(pathenv, ffd.cFileName);
354         ret |= load_sharedlibrary_plugins(filename.c_str(), vmdplugin);
355     } while (FindNextFile(hFind, &ffd) != 0 && vmdplugin->api == NULL);
356     FindClose(hFind);
357 #endif
358 
359     if (!ret)
360     {
361         printf("\nCould not open any VMD library.\n");
362         err = vmddlerror();
363         if (!err)
364         {
365             printf("Compiled with dlopen?\n");
366         }
367         else
368         {
369             printf("Last error:\n%s\n", err);
370         }
371         return 0;
372     }
373 
374     if (vmdplugin->api == nullptr)
375     {
376         printf("\nNo plugin for %s found\n", vmdplugin->filetype);
377         return 0;
378     }
379 
380     if (vmdplugin->api->abiversion < 10)
381     {
382         printf("\nPlugin and/or VMD is too old. At least VMD 1.8.6 is required.\n");
383         return 0;
384     }
385 
386     printf("\nUsing VMD plugin: %s (%s)\n", vmdplugin->api->name, vmdplugin->api->prettyname);
387 
388     return 1;
389 }
390 
read_first_vmd_frame(const char * fn,gmx_vmdplugin_t ** vmdpluginp,t_trxframe * fr)391 int read_first_vmd_frame(const char* fn, gmx_vmdplugin_t** vmdpluginp, t_trxframe* fr)
392 {
393     molfile_timestep_metadata_t* metadata = nullptr;
394     gmx_vmdplugin_t*             vmdplugin;
395 
396     snew(vmdplugin, 1);
397     *vmdpluginp = vmdplugin;
398     if (!load_vmd_library(fn, vmdplugin))
399     {
400         return 0;
401     }
402 
403     vmdplugin->handle = vmdplugin->api->open_file_read(fn, vmdplugin->filetype, &fr->natoms);
404 
405     if (!vmdplugin->handle)
406     {
407         fprintf(stderr, "\nError: could not open file '%s' for reading.\n", fn);
408         return 0;
409     }
410 
411     if (fr->natoms == MOLFILE_NUMATOMS_UNKNOWN)
412     {
413         fprintf(stderr, "\nFormat of file %s does not record number of atoms.\n", fn);
414         return 0;
415     }
416     else if (fr->natoms == MOLFILE_NUMATOMS_NONE)
417     {
418         fprintf(stderr, "\nNo atoms found by VMD plugin in file %s.\n", fn);
419         return 0;
420     }
421     else if (fr->natoms < 1) /*should not be reached*/
422     {
423         fprintf(stderr, "\nUnknown number of atoms %d for VMD plugin opening file %s.\n", fr->natoms, fn);
424         return 0;
425     }
426 
427     snew(fr->x, fr->natoms);
428 
429     vmdplugin->bV = false;
430     if (vmdplugin->api->abiversion > 10 && vmdplugin->api->read_timestep_metadata)
431     {
432         vmdplugin->api->read_timestep_metadata(vmdplugin->handle, metadata);
433         assert(metadata);
434         vmdplugin->bV = (metadata->has_velocities != 0);
435         if (vmdplugin->bV)
436         {
437             snew(fr->v, fr->natoms);
438         }
439     }
440     else
441     {
442         fprintf(stderr,
443                 "\nThis trajectory is being read with a VMD plug-in from before VMD"
444                 "\nversion 1.8, or from a trajectory that lacks time step metadata."
445                 "\nEither way, GROMACS cannot tell whether the trajectory has velocities.\n");
446     }
447     return 1;
448 }
449