μHAL (v2.8.17)
Part of the IPbus software repository
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
files.cpp
Go to the documentation of this file.
1/*
2---------------------------------------------------------------------------
3
4 This file is part of uHAL.
5
6 uHAL is a hardware access library and programming framework
7 originally developed for upgrades of the Level-1 trigger of the CMS
8 experiment at CERN.
9
10 uHAL is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 uHAL 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
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with uHAL. If not, see <http://www.gnu.org/licenses/>.
22
23
24 Andrew Rose, Imperial College, London
25 email: awr01 <AT> imperial.ac.uk
26
27 Marc Magrans de Abril, CERN
28 email: marc.magrans.de.abril <AT> cern.ch
29
30 Tom Williams, Rutherford Appleton Laboratory, Oxfordshire
31 email: tom.williams <AT> cern.ch
32
33---------------------------------------------------------------------------
34*/
35
37
38
39#include <fstream>
40#include <iostream>
41
42#include <wordexp.h>
43
44#include <boost/filesystem/path.hpp>
45#include <boost/filesystem/operations.hpp>
46#include <boost/asio/error.hpp>
47#include <boost/asio/io_service.hpp>
48#include <boost/asio/ip/tcp.hpp>
49#include <boost/asio/ip/udp.hpp>
50#include <boost/asio/streambuf.hpp>
51#include <boost/asio/read.hpp>
52#include <boost/asio/write.hpp>
53#include <boost/spirit/include/qi.hpp>
54
55#include "uhal/log/log.hpp"
56
57
58namespace uhal
59{
60 namespace utilities
61 {
62
63 void ParseSemicolonDelimitedUriList ( const std::string& aSemicolonDelimitedUriList , std::vector< std::pair<std::string, std::string> >& aUriList )
64 {
65 try
66 {
68 boost::spirit::qi::phrase_parse ( aSemicolonDelimitedUriList.begin() , aSemicolonDelimitedUriList.end() , lGrammar , boost::spirit::ascii::space , aUriList );
69 }
70 catch ( const std::exception& aExc )
71 {
72 exception::UriListParsingError lExc;
73 log ( lExc , "Expression " , Quote ( aSemicolonDelimitedUriList ) , " must be a semicolon delimeted list and all files must be in the form " , Quote ( "protocol://address" ) );
74 throw lExc;
75 }
76
77 log ( Debug() , "Parsed " , Quote ( aSemicolonDelimitedUriList ) , " to:" );
78
79 for (const auto& x: aUriList)
80 {
81 log ( Debug() , " > [" , x.first , "] " , Quote ( x.second ) );
82 }
83 }
84
85
86 void ShellExpandFilenameExpr ( const std::string& aFilenameExpr , const boost::filesystem::path& aParentPath , std::vector< boost::filesystem::path >& aFiles )
87 {
88 try
89 {
90 //struct which will store the shell expansions of the expression
91 wordexp_t lShellExpansion;
92 wordexp ( aFilenameExpr.c_str() , &lShellExpansion , 0 );
93
94 for ( std::size_t i = 0 ; i != lShellExpansion.we_wordc ; i++ )
95 {
96 boost::filesystem::path lPath ( lShellExpansion.we_wordv[i] );
97 log ( Debug() , "lPath was " , Quote ( lPath.c_str() ) );
98 log ( Debug() , "aParentPath is " , Quote ( aParentPath.c_str() ) );
99 lPath = boost::filesystem::absolute ( lPath , aParentPath );
100 log ( Debug() , "lPath now " , Quote ( lPath.c_str() ) );
101
102 if ( boost::filesystem::exists ( lPath ) )
103 {
104 if ( boost::filesystem::is_regular_file ( lPath ) )
105 {
106 aFiles.push_back ( lPath );
107 }
108 }
109 }
110
111 wordfree ( &lShellExpansion );
112 }
113 catch ( const std::exception& aExc )
114 {
115 uhal::exception::ExpandingShellExpressionFailed lExc;
116 log ( lExc , "Caught exception during shell expansion: " , Quote ( aExc.what() ) );
117 throw lExc;
118 }
119
120 if ( ! aFiles.size() )
121 {
122 uhal::exception::FileNotFound lExc;
123 log ( lExc , "No matching files for expression " , Quote ( aFilenameExpr ) , " with parent path " , Quote ( aParentPath.c_str() ) );
124 throw lExc;
125 }
126 else
127 {
128 log ( Debug() , "Shell expansion of " , Quote ( aFilenameExpr.c_str() ) , " returned:" );
129
130 for (const auto& lFile: aFiles)
131 log ( Debug() , " > [file] " , lFile.c_str() );
132 }
133 }
134
135
136 template < bool DebugInfo >
137 bool HttpGet ( const std::string& aURL , HttpResponseType& aResponse )
138 {
139 if ( DebugInfo )
140 {
141 try
142 {
143 log ( Info() , "Retrieving URL http://" , aURL );
144 }
145 catch ( const std::exception& aExc )
146 {
147 log ( Error() , "Exception " , Quote ( aExc.what() ) , " caught at " , ThisLocation() ); // Just debugging so although exception is worrying, it is not critical
148 }
149 }
150
151 std::pair<std::string, std::string> lURLPair;
152
153 try
154 {
155 //split at the first slash
156 boost::spirit::qi::phrase_parse ( aURL.begin() ,
157 aURL.end() ,
158 + ( boost::spirit::qi::char_ - "/" ) >> -boost::spirit::qi::lit ( "/" ) >> + ( boost::spirit::qi::char_ ) ,
159 boost::spirit::ascii::space ,
160 lURLPair );
161 }
162 catch ( const std::exception& aExc )
163 {
164 // log ( Error() , "Exception " , Quote ( aExc.what() ) , " caught at " , ThisLocation() );
165 return false;
166 }
167
168 boost::system::error_code lErrorCode ( boost::asio::error::host_not_found );
169 // The IO service everything will go through
170 boost::asio::io_service io_service;
171 // Get a list of endpoints corresponding to the server name.
172 boost::asio::ip::tcp::resolver resolver ( io_service );
173 boost::asio::ip::tcp::resolver::query query ( lURLPair.first , "http" );
174 boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve ( query );
175 boost::asio::ip::tcp::resolver::iterator end;
176 // Try each endpoint until we successfully establish a connection.
177 boost::asio::ip::tcp::socket socket ( io_service );
178
179 try
180 {
181 while ( lErrorCode && endpoint_iterator != end )
182 {
183 socket.close();
184 socket.connect ( *endpoint_iterator++, lErrorCode );
185 }
186
187 if ( lErrorCode )
188 {
189 throw boost::system::system_error ( lErrorCode );
190 }
191 }
192 catch ( const std::exception& aExc )
193 {
194 return false;
195 }
196
197 // Form the request. We specify the "Connection: close" header so that the server will close the socket after transmitting the response. This will allow us to treat all data up until the EOF as the content.
198 boost::asio::streambuf request;
199 std::ostream request_stream ( &request );
200 request_stream << "GET /" << lURLPair.second << " HTTP/1.0\r\n";
201 request_stream << "Host: " << lURLPair.first << "\r\n";
202 request_stream << "Accept: */*\r\n";
203 request_stream << "Connection: close\r\n\r\n";
204
205 try
206 {
207 // Send the request...
208 boost::asio::write ( socket , request , lErrorCode );
209
210 if ( lErrorCode )
211 {
212 throw boost::system::system_error ( lErrorCode );
213 }
214 }
215 catch ( const std::exception& aExc )
216 {
217 return false;
218 }
219
220 // ... and get the reply. First we need a buffer to write the reply in to...
221 static const int mDefaultBufferSize ( 65536 );
222 typedef std::vector<uint8_t> BufferType;
223 BufferType mBuffer ( mDefaultBufferSize , uint8_t ( 0 ) );
224 std::size_t lSize ( 0 );
225
226 // Just keep reading and, if we have not reached the EOF, extend the buffer and read some more.
227 try
228 {
229 while ( true )
230 {
231 lSize += boost::asio::read ( socket, boost::asio::buffer ( &mBuffer[lSize] , mDefaultBufferSize ) , lErrorCode );
232
233 if ( lErrorCode )
234 {
235 if ( lErrorCode == boost::asio::error::eof )
236 {
237 break;
238 }
239 else
240 {
241 throw boost::system::system_error ( lErrorCode );
242 }
243 }
244
245 mBuffer.insert ( mBuffer.end() , mDefaultBufferSize , uint8_t ( 0 ) );
246 }
247 }
248 catch ( const std::exception& aExc )
249 {
250 return false;
251 }
252
253 mBuffer.resize ( lSize );
254 //Parse the recieved data into an HttpResponseType object
255 grammars::HttpResponseGrammar lGrammar2;
256 boost::spirit::qi::phrase_parse ( mBuffer.begin() , mBuffer.end() , lGrammar2 , boost::spirit::ascii::space , aResponse );
257
258 if ( DebugInfo )
259 {
260 try
261 {
262 log ( Info() , "HTTP response parsed as:\n" , aResponse );
263 }
264 catch ( const std::exception& aExc )
265 {
266 log ( Error() , "EXCEPTION: " , aExc.what() , " caught in " , ThisLocation() , " Continuing." );
267 // Just debugging so although exception is worrying, it is not critical
268 }
269 }
270
271 if ( aResponse.method != "HTTP" || aResponse.status != 200 )
272 {
273 return false;
274 }
275
276 return true;
277 }
278
279
280 template bool HttpGet<false> ( const std::string& , HttpResponseType& );
281 template bool HttpGet<true> ( const std::string& , HttpResponseType& );
282
283
284 void OpenFileLocal ( const std::string& aFilenameExpr , const boost::filesystem::path& aParentPath , const detail::FileCallback_t& aCallback)
285 {
286 std::vector< boost::filesystem::path > lFilePaths;
287 uhal::utilities::ShellExpandFilenameExpr ( aFilenameExpr , aParentPath , lFilePaths );
288
289 for (const auto& lPath: lFilePaths)
290 {
291 std::ifstream lStr ( lPath.c_str() );
292
293 if ( !lStr.is_open() )
294 {
295 uhal::exception::CannotOpenFile lExc;
296 log ( lExc , "Failed to open file " , Quote ( lPath.c_str() ) );
297 throw lExc ;
298 }
299 else
300 {
301 lStr.seekg ( 0, std::ios::end );
302 std::vector<uint8_t> lFile ( lStr.tellg() , 0 );
303 lStr.seekg ( 0, std::ios::beg );
304 lStr.read ( ( char* ) & ( lFile[0] ) , lFile.size() );
305 aCallback ( std::string ( "file" ) , lPath , lFile);
306 }
307
308 lStr.close();
309 }
310 }
311
312
313 void OpenFileHttp ( const std::string& aURL , const detail::FileCallback_t& aCallback )
314 {
315 HttpResponseType lHttpResponse;
316
317 if ( ! uhal::utilities::HttpGet<true> ( aURL , lHttpResponse ) )
318 {
319 uhal::exception::CannotOpenFile lExc;
320 log ( lExc , "Failed to download file " , Quote ( aURL ) );
321 throw lExc;
322 }
323
324 boost::filesystem::path lFilePath = boost::filesystem::path ( aURL );
325 aCallback ( std::string ( "http" ) , lFilePath , lHttpResponse.content );
326 }
327
328
329 void OpenFile ( const std::string& aProtocol , const std::string& aFilenameExpr , const boost::filesystem::path& aParentPath , const detail::FileCallback_t& aCallback )
330 {
331 if ( aProtocol == "file" )
332 {
333 uhal::utilities::OpenFileLocal ( aFilenameExpr , aParentPath , aCallback );
334 }
335 else if ( aProtocol == "http" )
336 {
337 uhal::utilities::OpenFileHttp ( aFilenameExpr , aCallback );
338 }
339 else
340 {
341 uhal::exception::NonSupportedUriProtocol lExc;
342 log ( lExc , "The protocol " , Quote ( aProtocol ) , " for file " , Quote ( aFilenameExpr ) , " is not supported." );
343 log ( lExc , "The supported protocols are " , Quote ( "file://" ) , " and " , Quote ( "http://" ) );
344 throw lExc;
345 }
346 }
347 }
348}
349
#define ThisLocation()
std::function< void(const std::string &, const boost::filesystem::path &, std::vector< uint8_t > &aCallback)> FileCallback_t
Definition: files.hpp:102
void ParseSemicolonDelimitedUriList(const std::string &aSemicolonDelimitedUriList, std::vector< std::pair< std::string, std::string > > &aUriList)
Parse a semicolon delimited list of URIs into a vector of protocol/address pairs.
Definition: files.cpp:63
bool HttpGet(const std::string &aURL, HttpResponseType &aResponse)
Retrieve a file by HTTP.
Definition: files.cpp:137
void OpenFileLocal(const std::string &aFilenameExpr, const boost::filesystem::path &aParentPath, const detail::FileCallback_t &aCallback)
Given a linux shell expression, open all files which match it and call the callback function on each ...
Definition: files.cpp:284
template bool HttpGet< true >(const std::string &, HttpResponseType &)
void ShellExpandFilenameExpr(const std::string &aFilenameExpr, const boost::filesystem::path &aParentPath, std::vector< boost::filesystem::path > &aFiles)
Perform shell expansion of a linux shell expression ( e.g.
Definition: files.cpp:86
template bool HttpGet< false >(const std::string &, HttpResponseType &)
void OpenFileHttp(const std::string &aURL, const detail::FileCallback_t &aCallback)
Given a URL, retrieve the file and call the callback function on each of them.
Definition: files.cpp:313
void OpenFile(const std::string &aProtocol, const std::string &aFilenameExpr, const boost::filesystem::path &aParentPath, const detail::FileCallback_t &aCallback)
Given a protocol and either a URL or a linux shell expression, open the file and call the callback fu...
Definition: files.cpp:329
DebugLevel Debug
Definition: LogLevels.cpp:133
_Quote< T > Quote(const T &aT)
ErrorLevel Error
Definition: LogLevels.cpp:61
void log(FatalLevel &aFatal, const T0 &aArg0)
Function to add a log entry at Fatal level.
Definition: log.hxx:18
InfoLevel Info
Definition: LogLevels.cpp:115
Struct to store an http response received from a server when parsed by boost spirit.
std::string method
the http transport method
int status
the response status
std::vector< uint8_t > content
parsed message content
The BOOST::SPIRIT grammar for parsing the Semicolon delimited URI list into a vector of protocol-URI ...