locate_images.cpp revision 8cfa702f803c5ef6a2b062a489a1b2cf66b45b5e
18cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd/** 28cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * @file locate_images.cpp 38cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * Command-line helper 48cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * 58cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * @remark Copyright 2002 OProfile authors 68cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * @remark Read the file COPYING 78cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * 88cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * @author Philippe Elie 98cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * @author John Levon 108cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd */ 118cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 128cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include "file_manip.h" 138cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include "locate_images.h" 148cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include "string_manip.h" 158cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 168cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include <cerrno> 178cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include <iostream> 188cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include <sstream> 198cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include <cstdlib> 208cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 218cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddusing namespace std; 228cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 238cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 248cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddint extra_images::suid; 258cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 268cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddextra_images::extra_images() 278cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd : 288cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd uid(++suid) 298cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{ 308cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd} 318cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 328cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 338cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddvoid extra_images::populate(vector<string> const & paths, 348cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd string const & prefix_path) 358cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{ 368cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd vector<string>::const_iterator cit = paths.begin(); 378cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd vector<string>::const_iterator end = paths.end(); 388cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd for (; cit != end; ++cit) { 398cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd string const path = op_realpath(prefix_path + *cit); 408cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd list<string> file_list; 418cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd create_file_list(file_list, path, "*", true); 428cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd list<string>::const_iterator lit = file_list.begin(); 438cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd list<string>::const_iterator lend = file_list.end(); 448cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd for (; lit != lend; ++lit) { 458cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd value_type v(op_basename(*lit), op_dirname(*lit)); 468cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd images.insert(v); 478cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 488cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 498cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd} 508cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 518cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 528cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddvoid extra_images::populate(vector<string> const & paths, 538cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd string const & archive_path_, 548cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd string const & root_path_) 558cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{ 568cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd archive_path = archive_path_; 578cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (!archive_path.empty()) 588cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd archive_path = op_realpath(archive_path); 598cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 608cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd root_path = op_realpath(root_path_); 618cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (!root_path.empty()) 628cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd root_path = op_realpath(root_path); 638cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 648cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (root_path.empty() && archive_path.empty()) 658cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd populate(paths, ""); 668cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (!archive_path.empty()) 678cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd populate(paths, archive_path); 688cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (!root_path.empty() && root_path != archive_path) 698cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd populate(paths, root_path); 708cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd} 718cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 728cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 738cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddvector<string> const extra_images::find(string const & name) const 748cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{ 758cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd extra_images::matcher match(name); 768cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return find(match); 778cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd} 788cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 798cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 808cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddvector<string> const 818cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddextra_images::find(extra_images::matcher const & match) const 828cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{ 838cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd vector<string> matches; 848cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 858cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd const_iterator cit = images.begin(); 868cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd const_iterator end = images.end(); 878cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 888cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd for (; cit != end; ++cit) { 898cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (match(cit->first)) 908cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd matches.push_back(cit->second + '/' + cit->first); 918cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 928cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 938cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return matches; 948cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd} 958cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 968cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 978cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddnamespace { 988cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 998cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd/** 1008cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * Function object for matching a module filename, which 1018cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * has its own special mangling rules in 2.6 kernels. 1028cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd */ 1038cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstruct module_matcher : public extra_images::matcher { 1048cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddpublic: 1058cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd explicit module_matcher(string const & s) 1068cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd : extra_images::matcher(s) {} 1078cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1088cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd virtual bool operator()(string const & candidate) const { 1098cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (candidate.length() != value.length()) 1108cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return false; 1118cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1128cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd for (string::size_type i = 0 ; i < value.length() ; ++i) { 1138cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (value[i] == candidate[i]) 1148cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd continue; 1158cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (value[i] == '_' && 1168cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd (candidate[i] == ',' || candidate[i] == '-')) 1178cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd continue; 1188cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return false; 1198cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 1208cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1218cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return true; 1228cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 1238cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}; 1248cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1258cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd} // anon namespace 1268cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1278cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstring const extra_images::locate_image(string const & image_name, 1288cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd image_error & error, bool fixup) const 1298cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{ 1308cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd // Skip search since root_path can be non empty and we want 1318cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd // to lookup only in root_path in this case. 1328cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (!archive_path.empty()) { 1338cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd string image = op_realpath(archive_path + image_name); 1348cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (op_file_readable(image)) { 1358cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd error = image_ok; 1368cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return fixup ? image : image_name; 1378cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 1388cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1398cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (errno == EACCES) { 1408cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd error = image_unreadable; 1418cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return image_name; 1428cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 1438cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 1448cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1458cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd // We catch a case where root_path.empty() since we skipped a 1468cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd // search in "/" above when archive_path is empty. The case where 1478cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd // root_path.empty() && archive_path.empty() is the normal one, none 1488cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd // of --root or archive: as been given on command line. 1498cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (!root_path.empty() || archive_path.empty()) { 1508cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd string image = op_realpath(root_path + image_name); 1518cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (op_file_readable(image)) { 1528cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd error = image_ok; 1538cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return fixup ? image : image_name; 1548cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 1558cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 1568cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1578cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd error = image_not_found; 1588cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return image_name; 1598cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd} 1608cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1618cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstring const extra_images::find_image_path(string const & image_name, 1628cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd image_error & error, bool fixup) const 1638cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{ 1648cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd error = image_ok; 1658cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1668cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd string const image = locate_image(image_name, error, fixup); 1678cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (error != image_not_found) 1688cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return image; 1698cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1708cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd string const base = op_basename(image); 1718cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1728cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd vector<string> result = find(base); 1738cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1748cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd // not found, try a module search 1758cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (result.empty()) 1768cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd result = find(module_matcher(base + ".ko")); 1778cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1788cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (result.empty()) { 1798cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd error = image_not_found; 1808cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return image_name; 1818cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 1828cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1838cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (result.size() == 1) { 1848cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd error = image_ok; 1858cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return fixup ? result[0] : image_name; 1868cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 1878cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1888cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd // We can't get multiple result except if only one result is prefixed 1898cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd // by archive_path or by root_path. 1908cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd size_t count = 0; 1918cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd size_t index = 0; 1928cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd for (size_t i = 0; i < result.size() && count < 2; ++i) { 1938cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (is_prefix(result[i], archive_path)) { 1948cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd index = i; 1958cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd ++count; 1968cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 1978cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 1988cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 1998cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (count == 0) { 2008cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd for (size_t i = 0; i < result.size() && count < 2; ++i) { 2018cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (is_prefix(result[i], root_path)) { 2028cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd index = i; 2038cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd ++count; 2048cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 2058cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 2068cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 2078cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 2088cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (count == 1) { 2098cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd error = image_ok; 2108cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return fixup ? result[index] : image_name; 2118cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd } 2128cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 2138cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd error = image_multiple_match; 2148cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return image_name; 2158cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd} 2168cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 2178cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd 2188cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstring extra_images::strip_path_prefix(string const & image) const 2198cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{ 2208cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (archive_path.length() && is_prefix(image, archive_path)) 2218cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return image.substr(archive_path.size()); 2228cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd if (root_path.length() && is_prefix(image, root_path)) 2238cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return image.substr(root_path.size()); 2248cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd return image; 2258cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd} 226