2 // Copyright (C) 2017 Red Hat Inc.
4 // This file is part of systemtap, and is free software. You can
5 // redistribute it and/or modify it under the terms of the GNU General
6 // Public License (GPL); either version 2, or (at your option) any
11 #ifdef HAVE_HTTP_SUPPORT
13 #include "client-http.h"
16 #include "elaborate.h"
27 #include <curl/curl.h>
28 #include <curl/easy.h>
29 #include <json-c/json.h>
32 #include <rpm/rpmlib.h>
33 #include <rpm/header.h>
34 #include <rpm/rpmts.h>
35 #include <rpm/rpmdb.h>
37 #include <elfutils/libdwfl.h>
38 #include <elfutils/libdw.h>
48 http_client (systemtap_session
&s
):
54 ~http_client () {if (curl
) curl_easy_cleanup(curl
);};
58 std::map
<std::string
, std::string
> header_values
;
59 enum download_type
{json_type
, file_type
};
61 bool download (const std::string
& url
, enum download_type type
);
62 bool post (const std::string
& url
, std::vector
<std::tuple
<std::string
, std::string
>> &request_parameters
);
63 void add_script_file (std::string script_type
, std::string script_file
);
64 void add_module (std::string module
);
65 void get_header_field (const std::string
& data
, const std::string
& field
);
66 static size_t get_data_shim (void *ptr
, size_t size
, size_t nitems
, void *client
);
67 static size_t get_file (void *ptr
, size_t size
, size_t nitems
, FILE * stream
);
68 static size_t get_header_shim (void *ptr
, size_t size
, size_t nitems
, void *client
);
69 std::string
get_rpmname (std::string
& pathname
);
70 void get_buildid (string fname
);
71 void get_kernel_buildid (void);
74 size_t get_header (void *ptr
, size_t size
, size_t nitems
);
75 size_t get_data (void *ptr
, size_t size
, size_t nitems
);
76 static int process_buildid_shim (Dwfl_Module
*dwflmod
, void **userdata
, const char *name
,
77 Dwarf_Addr base
, void *client
);
78 int process_buildid (Dwfl_Module
*dwflmod
);
79 std::vector
<std::string
> script_files
;
80 std::vector
<std::string
> modules
;
81 std::vector
<std::tuple
<std::string
, std::string
>> buildids
;
85 std::string
*location
;
89 // TODO is there a better way than making this static?
90 static http_client
*http
;
94 http_client::get_data_shim (void *ptr
, size_t size
, size_t nitems
, void *client
)
96 http_client
*http
= static_cast<http_client
*>(client
);
98 return http
->get_data (ptr
, size
, nitems
);
101 // Parse the json data at PTR having SIZE and NITEMS into root
104 http_client::get_data (void *ptr
, size_t size
, size_t nitems
)
106 string
data ((const char *) ptr
, (size_t) size
* nitems
);
108 if (data
.front () == '{')
110 enum json_tokener_error json_error
;
111 root
= json_tokener_parse_verbose (data
.c_str(), &json_error
);
114 clog
<< json_object_to_json_string (root
) << endl
;
116 throw SEMANTIC_ERROR (json_tokener_error_desc (json_error
));
118 return size
* nitems
;
123 http_client::get_header_shim (void *ptr
, size_t size
, size_t nitems
, void *client
)
125 http_client
*http
= static_cast<http_client
*>(client
);
127 return http
->get_header (ptr
, size
, nitems
);
131 // Extract header values at PTR having SIZE and NITEMS into header_values
134 http_client::get_header (void *ptr
, size_t size
, size_t nitems
)
136 string
data ((const char *) ptr
, (size_t) size
* nitems
);
138 unsigned long colon
= data
.find(':');
139 if (colon
!= string::npos
)
141 string key
= data
.substr (0, colon
);
142 string value
= data
.substr (colon
+ 2, data
.length() - colon
- 4);
143 header_values
[key
] = value
;
146 return size
* nitems
;
150 // Put the data, e.g. <module>.ko at PTR having SIZE and NITEMS into STREAM
153 http_client::get_file (void *ptr
, size_t size
, size_t nitems
, std::FILE * stream
)
156 written
= fwrite (ptr
, size
, nitems
, stream
);
157 std::fflush (stream
);
162 // Do a download of type TYPE from URL
165 http_client::download (const std::string
& url
, http_client::download_type type
)
167 struct curl_slist
*headers
= NULL
;
170 curl_easy_reset (curl
);
171 curl
= curl_easy_init ();
172 curl_global_init (CURL_GLOBAL_ALL
);
173 curl_easy_setopt (curl
, CURLOPT_URL
, url
.c_str ());
174 curl_easy_setopt (curl
, CURLOPT_FOLLOWLOCATION
, 1L);
175 curl_easy_setopt (curl
, CURLOPT_NOSIGNAL
, 1); //Prevent "longjmp causes uninitialized stack frame" bug
176 curl_easy_setopt (curl
, CURLOPT_ACCEPT_ENCODING
, "deflate");
177 headers
= curl_slist_append (headers
, "Accept: */*");
178 headers
= curl_slist_append (headers
, "Content-Type: text/html");
179 curl_easy_setopt (curl
, CURLOPT_HTTPHEADER
, headers
);
180 curl_easy_setopt (curl
, CURLOPT_HTTPGET
, 1);
182 if (type
== json_type
)
184 curl_easy_setopt (curl
, CURLOPT_WRITEDATA
, http
);
185 curl_easy_setopt (curl
, CURLOPT_WRITEFUNCTION
, http_client::get_data_shim
);
187 else if (type
== file_type
)
189 std::string filename
= url
;
190 std::string ko_suffix
= ".ko\"";
191 std::string filepath
;
192 if (filename
.back() == '/')
193 filename
.erase(filename
.length()-1);
195 if (std::equal(ko_suffix
.rbegin(), ko_suffix
.rend(), filename
.rbegin()))
196 filepath
= s
.tmpdir
+ "/" + s
.module_name
+ ".ko";
198 filepath
= s
.tmpdir
+ "/" + filename
.substr (filename
.rfind ('/')+1);
201 clog
<< "Downloaded " + filepath
<< endl
;
202 std::FILE *File
= std::fopen (filepath
.c_str(), "wb");
203 curl_easy_setopt (curl
, CURLOPT_WRITEDATA
, File
);
204 curl_easy_setopt (curl
, CURLOPT_WRITEFUNCTION
, http_client::get_file
);
206 curl_easy_setopt (curl
, CURLOPT_HEADERDATA
, http
);
207 curl_easy_setopt (curl
, CURLOPT_HEADERFUNCTION
, http_client::get_header_shim
);
209 CURLcode res
= curl_easy_perform (curl
);
213 clog
<< "curl_easy_perform() failed: " << curl_easy_strerror (res
) << endl
;
221 // Get the rpm corresponding to SEARCH_FILE
224 http_client::get_rpmname (std::string
&search_file
)
228 rpmdbMatchIterator mi
;
234 rpmReadConfigFiles (NULL
, NULL
);
237 { RPMTAG_ARCH
, RPMTAG_EVR
, RPMTAG_FILENAMES
, RPMTAG_NAME
};
247 mi
= rpmtsInitIterator (ts
, RPMDBI_PACKAGES
, NULL
, 0);
248 while (NULL
!= (hdr
= rpmdbNextIterator (mi
)))
250 for (unsigned int i
= 0; i
< (sizeof (metrics
) / sizeof (int)); i
++)
252 headerGet (hdr
, metrics
[i
], td
, HEADERGET_EXT
);
255 case RPM_STRING_TYPE
:
257 const char *rpmval
= rpmtdGetString (td
);
261 rpmhdr
.arch
= strdup (rpmval
);
264 rpmhdr
.name
= strdup (rpmval
);
267 rpmhdr
.evr
= strdup (rpmval
);
271 case RPM_STRING_ARRAY_TYPE
:
274 strings
= (char**)td
->data
;
275 rpmhdr
.filename
= "";
277 for (unsigned int idx
= 0; idx
< td
->count
; idx
++)
279 if (strcmp (strings
[idx
], search_file
.c_str()) == 0)
280 rpmhdr
.filename
= strdup (strings
[idx
]);
287 if (metrics
[i
] == RPMTAG_EVR
&& rpmhdr
.filename
.length())
289 rpmdbFreeIterator (mi
);
291 return rpmhdr
.name
+ "-" + rpmhdr
.evr
+ "." + rpmhdr
.arch
;
298 rpmdbFreeIterator (mi
);
305 // Put the buildid for DWFLMOD into buildids
308 http_client::process_buildid (Dwfl_Module
*dwflmod
)
311 dwfl_module_info (dwflmod
, NULL
, NULL
, NULL
, NULL
, NULL
, &fname
, NULL
);
314 int build_id_len
= 0;
315 unsigned char *build_id_bits
;
316 GElf_Addr build_id_vaddr
;
321 dwfl_module_getelf (dwflmod
, &bias
);
322 build_id_len
= dwfl_module_build_id (dwflmod
,
323 (const unsigned char **)&build_id_bits
,
326 for (int i
= 0; i
< build_id_len
; i
++)
329 code
= asprintf (&result
, "%s%02x", result
, *(build_id_bits
+i
));
331 code
= asprintf (&result
, "%02x", *(build_id_bits
+i
));
336 http
->buildids
.push_back(make_tuple(fname
, result
));
343 http_client::process_buildid_shim (Dwfl_Module
*dwflmod
,
344 void **userdata
__attribute__ ((unused
)),
345 const char *name
__attribute__ ((unused
)),
346 Dwarf_Addr base
__attribute__ ((unused
)),
349 http_client
*http
= static_cast<http_client
*>(client
);
351 return http
->process_buildid (dwflmod
);
355 // Do the setup for getting the buildid for FNAME
358 http_client::get_buildid (string fname
)
362 if ((fd
= open (fname
.c_str(), O_RDONLY
)) < 0)
364 clog
<< "can't open " << fname
;
368 static const Dwfl_Callbacks callbacks
=
370 dwfl_build_id_find_elf
,
371 dwfl_standard_find_debuginfo
,
372 dwfl_offline_section_address
,
375 Dwfl
*dwfl
= dwfl_begin (&callbacks
);
380 if (dwfl_report_offline (dwfl
, fname
.c_str(), fname
.c_str(), fd
) == NULL
)
384 dwfl_report_end (dwfl
, NULL
, NULL
);
385 dwfl_getmodules (dwfl
, process_buildid_shim
, http
, 0);
393 http_client::get_kernel_buildid (void)
395 const char *notesfile
= "/sys/kernel/notes";
396 int fd
= open (notesfile
, O_RDONLY
);
403 unsigned char data
[8192];
406 ssize_t n
= read (fd
, buf
.data
, sizeof buf
);
412 unsigned char *p
= buf
.data
;
413 while (p
< &buf
.data
[n
])
415 /* No translation required since we are reading the native kernel. */
416 GElf_Nhdr
*nhdr
= (GElf_Nhdr
*) p
;
418 unsigned char *name
= p
;
419 p
+= (nhdr
->n_namesz
+ 3) & -4U;
420 unsigned char *bits
= p
;
421 p
+= (nhdr
->n_descsz
+ 3) & -4U;
423 if (p
<= &buf
.data
[n
]
424 && nhdr
->n_type
== NT_GNU_BUILD_ID
425 && nhdr
->n_namesz
== sizeof "GNU"
426 && !memcmp (name
, "GNU", sizeof "GNU"))
431 for (unsigned int i
= 0; i
< nhdr
->n_descsz
; i
++)
434 code
= asprintf (&result
, "%s%02x", result
, *(bits
+i
));
436 code
= asprintf (&result
, "%02x", *(bits
+i
));
440 http
->buildids
.push_back(make_tuple("kernel", result
));
447 // Post REQUEST_PARAMETERS, script_files, modules, buildids to URL
450 http_client::post (const std::string
& url
,
451 std::vector
<std::tuple
<std::string
, std::string
>> &request_parameters
)
453 struct curl_slist
*headers
=NULL
;
454 int still_running
= false;
455 struct curl_httppost
*formpost
=NULL
;
456 struct curl_httppost
*lastptr
=NULL
;
458 static const char buf
[] = "Expect:";
459 headers
= curl_slist_append (headers
, buf
);
461 for (vector
<std::tuple
<std::string
, std::string
>>::const_iterator it
= request_parameters
.begin ();
462 it
!= request_parameters
.end ();
465 string parm_type
= get
<0>(*it
);
466 char *parm_data
= (char*)get
<1>(*it
).c_str();
467 curl_formadd (&formpost
,
469 CURLFORM_COPYNAME
, parm_type
.c_str(),
470 CURLFORM_COPYCONTENTS
, parm_data
,
474 // Fill in the file upload field; libcurl will load data from the given file name
475 for (vector
<std::string
>::const_iterator it
= script_files
.begin ();
476 it
!= script_files
.end ();
479 string script_file
= (*it
);
480 string script_base
= basename (script_file
.c_str());
482 curl_formadd (&formpost
,
484 CURLFORM_COPYNAME
, script_base
.c_str(),
485 CURLFORM_FILE
, script_file
.c_str(),
488 curl_formadd (&formpost
,
490 CURLFORM_COPYNAME
, "files",
491 CURLFORM_COPYCONTENTS
, script_file
.c_str(),
494 // FIXME: There is no guarantee that the first file in
495 // script_files is a script - "stap -I foo/ -e '...script...'".
497 // script name is not in cmd_args so add it manually
498 if (it
== script_files
.begin())
499 curl_formadd (&formpost
,
501 CURLFORM_COPYNAME
, "cmd_args",
502 CURLFORM_COPYCONTENTS
, script_base
.c_str(),
508 for (vector
<std::string
>::const_iterator it
= modules
.begin ();
509 it
!= modules
.end ();
512 string module
= (*it
);
513 std::stringstream ss
;
515 string bid_module
= std::get
<0>(buildids
[bid_idx
]);
516 string buildid
= std::get
<1>(buildids
[bid_idx
]);
519 << "\"package\" : \"" << module
520 << "\", \"filename\" : \"" << bid_module
521 << "\", \"id\" : \"" << buildid
523 curl_formadd (&formpost
,
525 CURLFORM_COPYNAME
, "package info",
526 CURLFORM_CONTENTTYPE
, "application/json",
527 CURLFORM_COPYCONTENTS
, ss
.str().c_str(),
532 multi_handle
= curl_multi_init();
534 curl_easy_setopt (curl
, CURLOPT_URL
, url
.c_str());
535 curl_easy_setopt (curl
, CURLOPT_HTTPHEADER
, headers
);
536 curl_easy_setopt (curl
, CURLOPT_HTTPPOST
, formpost
);
538 // Mostly for debugging
541 curl_easy_setopt (curl
, CURLOPT_VERBOSE
, 1L);
542 CURL
*db_curl
= curl_easy_init();
543 clog
<< "BEGIN dump post data" << endl
;
544 curl_easy_setopt(db_curl
, CURLOPT_URL
, "http://httpbin.org/post");
545 curl_easy_setopt (db_curl
, CURLOPT_HTTPHEADER
, headers
);
546 curl_easy_setopt (db_curl
, CURLOPT_HTTPPOST
, formpost
);
547 CURLcode res
= curl_easy_perform (db_curl
);
549 clog
<< "curl_easy_perform() failed: " << curl_easy_strerror (res
) << endl
;
550 clog
<< "END dump post data" << endl
;
551 curl_easy_cleanup (db_curl
);
554 curl_multi_add_handle (multi_handle
, curl
);
556 curl_multi_perform (multi_handle
, &still_running
);
559 struct timeval timeout
;
560 int rc
; // select() return code
561 CURLMcode mc
; // curl_multi_fdset() return code
568 long curl_timeo
= -1;
574 // set a suitable timeout to play around with
578 curl_multi_timeout (multi_handle
, &curl_timeo
);
581 timeout
.tv_sec
= curl_timeo
/ 1000;
582 if (timeout
.tv_sec
> 1)
585 timeout
.tv_usec
= (curl_timeo
% 1000) * 1000;
588 // get file descriptors from the transfers
589 mc
= curl_multi_fdset (multi_handle
, &fdread
, &fdwrite
, &fdexcep
, &maxfd
);
593 clog
<< "curl_multi_fdset() failed" << curl_multi_strerror (mc
) << endl
;
597 /* On success the value of maxfd is guaranteed to be >= -1. We call
598 select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
599 no fds ready yet so we call select(0, ...)to sleep 100ms,
600 the minimum suggested value */
604 struct timeval wait
= { 0, 100 * 1000 }; // 100ms
605 rc
= select (0, NULL
, NULL
, NULL
, &wait
);
608 rc
= select (maxfd
+1, &fdread
, &fdwrite
, &fdexcep
, &timeout
);
617 curl_multi_perform (multi_handle
, &still_running
);
620 } while (still_running
);
622 curl_multi_cleanup (multi_handle
);
624 curl_formfree (formpost
);
626 curl_slist_free_all (headers
);
632 // Add SCRIPT_FILE having SCRIPT_TYPE to script_files
635 http_client::add_script_file (std::string script_type
, std::string script_file
)
637 if (script_type
== "tapset")
638 script_files
.push_back (script_file
);
640 script_files
.insert(script_files
.begin(), script_file
);
644 // Add MODULE to modules
647 http_client::add_module (std::string module
)
649 modules
.push_back (module
);
654 http_client_backend::initialize ()
656 http
= new http_client (s
);
657 request_parameters
.clear();
658 request_files
.clear();
663 http_client_backend::package_request ()
669 http_client_backend::find_and_connect_to_server ()
671 s
.probes
[0]->print (clog
);
673 http
->add_module ("kernel." + s
.kernel_release
);
674 http
->get_kernel_buildid ();
676 for (set
<std::string
>::const_iterator i
= s
.unwindsym_modules
.begin();
677 i
!= s
.unwindsym_modules
.end();
680 string module
= (*i
);
681 string rpmname
= http
->get_rpmname (module
);
682 http
->get_buildid (module
);
683 http
->add_module (rpmname
);
686 for (vector
<std::string
>::const_iterator i
= s
.http_servers
.begin ();
687 i
!= s
.http_servers
.end ();
689 if (http
->download (*i
+ "/builds", http
->json_type
))
692 if (! http
->post (http
->host
+ "/builds", request_parameters
))
700 http_client_backend::unpack_response ()
706 http_client_backend::process_response ()
709 std::string::size_type found
= http
->host
.find ("/builds");
711 std::map
<std::string
, std::string
>::iterator it_loc
;
712 it_loc
= http
->header_values
.find("Location");
713 if (it_loc
== http
->header_values
.end())
714 clog
<< "Cannot get location from server" << endl
;
715 if (found
!= std::string::npos
)
716 uri
= http
->host
.substr (0, found
) + http
->header_values
["Location"];
718 uri
= http
->host
+ http
->header_values
["Location"];
722 int retry
= std::stoi (http
->header_values
["Retry-After"], nullptr, 10);
724 clog
<< "Waiting " << retry
<< " seconds" << endl
;
726 if (http
->download (http
->host
+ http
->header_values
["Location"], http
->json_type
))
729 json_object_object_get_ex (http
->root
, "files", &files
);
732 clog
<< "No files received from server" << endl
;
735 for (int k
= 0; k
< json_object_array_length (files
); k
++)
737 json_object
*files_element
= json_object_array_get_idx (files
, k
);
739 found
= json_object_object_get_ex (files_element
, "location", &loc
);
740 string location
= json_object_get_string (loc
);
741 http
->download (http
->host
+ location
, http
->file_type
);
747 json_object
*stdio_loc
;
748 found
= json_object_object_get_ex (http
->root
, "stderr_location", &stdio_loc
);
749 string stdio_loc_str
= json_object_get_string (stdio_loc
);
750 http
->download (http
->host
+ stdio_loc_str
, http
->file_type
);
752 std::ifstream
ferr (s
.tmpdir
+ "/stderr");
754 std::cout
<< ferr
.rdbuf() << endl
;
757 found
= json_object_object_get_ex (http
->root
, "stdout_location", &stdio_loc
);
758 stdio_loc_str
= json_object_get_string (stdio_loc
);
759 http
->download (http
->host
+ stdio_loc_str
, http
->file_type
);
761 std::ifstream
fout (s
.tmpdir
+ "/stdout");
763 std::cout
<< fout
.rdbuf() << endl
;
770 http_client_backend::add_protocol_version (const std::string
&version
)
772 // Add the protocol version (so the server can ensure we're
774 request_parameters
.push_back(make_tuple("version", version
));
780 http_client_backend::add_sysinfo ()
782 request_parameters
.push_back(make_tuple("kver", s
.kernel_release
));
783 request_parameters
.push_back(make_tuple("arch", s
.architecture
));
785 vector
<string
> distro_info
;
787 get_distro_info (distro_info
);
788 if (! distro_info
.empty())
790 std::replace(distro_info
[0].begin(), distro_info
[0].end(), '\n', ' ');
791 std::replace(distro_info
[1].begin(), distro_info
[1].end(), '\n', ' ');
792 request_parameters
.push_back(make_tuple("distro_name", distro_info
[0]));
793 request_parameters
.push_back(make_tuple("distro_version", distro_info
[1]));
800 add_tapsets (const char *name
, const struct stat
*status
__attribute__ ((unused
)), int type
)
803 http
->add_script_file ("tapset", name
);
810 http_client_backend::include_file_or_directory (const std::string
&script_type
,
811 const std::string
&script_file
)
813 // FIXME: this is going to be interesting. We can't add a whole
814 // directory at one shot, we'll have to traverse the directory and
815 // add each file, preserving the directory structure somehow.
816 if (script_type
== "tapset")
817 ftw (script_file
.c_str(), add_tapsets
, 1);
819 http
->add_script_file (script_type
, script_file
);
824 http_client_backend::add_tmpdir_file (const std::string
&file
)
826 request_files
.push_back(make_tuple("files", file
));
831 http_client_backend::add_cmd_arg (const std::string
&arg
)
833 request_parameters
.push_back(make_tuple("cmd_args", arg
));
838 http_client_backend::add_localization_variable (const std::string
&,
841 // FIXME: We'll probably just add to the request_parameters here.
846 http_client_backend::add_mok_fingerprint (const std::string
&)
848 // FIXME: We'll probably just add to the request_parameters here.
852 #endif /* HAVE_HTTP_SUPPORT */