diff --git a/.gitignore b/.gitignore index 31e19cf8d0..0253803e91 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,7 @@ src/nomos/agent_tests/Functional/nomos-Xunit.xml src/nomos/agent_tests/Functional/nomos-regression-test.html src/nomos/agent_tests/Functional/report.d src/nomos/agent_tests/Unit/test_nomos +src/ojo/agent/ojo src/pkgagent/agent/pkgagent src/scheduler/agent/defconf/init.d/fossology src/scheduler/agent/fo_cli diff --git a/.travis.yml b/.travis.yml index d3530f07c4..bd3a58482b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,8 @@ addons: - genisoimage - libboost-program-options-dev - libboost-regex-dev + - libboost-system-dev + - libboost-filesystem-dev - libglib2.0-dev - libcppunit-dev - libcunit1-dev diff --git a/Dockerfile b/Dockerfile index e7ddc0cd3d..fddc0748c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,7 @@ COPY ./src/copyright/mod_deps ./src/copyright/ COPY ./src/delagent/mod_deps ./src/delagent/ COPY ./src/mimetype/mod_deps ./src/mimetype/ COPY ./src/nomos/mod_deps ./src/nomos/ +COPY ./src/ojo/mod_deps ./src/ojo/ COPY ./src/pkgagent/mod_deps ./src/pkgagent/ COPY ./src/scheduler/mod_deps ./src/scheduler/ COPY ./src/ununpack/mod_deps ./src/ununpack/ diff --git a/debian/control b/debian/control index ced8402a19..810c102164 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: fossology Section: utils Priority: extra Maintainer: Michael Jaeger -Build-Depends: debhelper, libglib2.0-dev, libmagic-dev, libxml2-dev, libmxml-dev, libtext-template-perl, librpm-dev, subversion, rpm, libpcre3-dev, libssl-dev, postgresql-server-dev-all, libboost-regex-dev, libboost-program-options-dev, libjsoncpp-dev, libjson-c-dev, php5-cli|php7.0-cli|php7.2-cli, php-mbstring|php5, php-zip|php5, php-xml|php5 +Build-Depends: debhelper, libglib2.0-dev, libmagic-dev, libxml2-dev, libmxml-dev, libtext-template-perl, librpm-dev, subversion, rpm, libpcre3-dev, libssl-dev, postgresql-server-dev-all, libboost-regex-dev, libboost-program-options-dev, libjsoncpp-dev, libjson-c-dev, php5-cli|php7.0-cli|php7.2-cli, php-mbstring|php5, php-zip|php5, php-xml|php5, libboost-system-dev, libboost-filesystem-dev Standards-Version: 3.9.1 Homepage: http://fossology.org @@ -236,6 +236,18 @@ Description: architecture for analyzing software, ninka . This package contains the ninka wrapper agent programs and their resources. +Package: fossology-ojo +Architecture: any +Depends: fossology-common, ${shlibs:Depends}, ${misc:Depends} +Description: architecture for analyzing software, ojo + The FOSSology project is a web based framework that allows you to + upload software to be picked apart and then analyzed by software agents + which produce results that are then browsable via the web interface. + Existing agents include license analysis, metadata extraction, and MIME + type identification. + . + This package contains the ojo agent programs and their resources. + Package: fossology-decider Architecture: any Depends: fossology-common, ${misc:Depends} diff --git a/debian/rules b/debian/rules index dcc13520c9..4dabeaad22 100755 --- a/debian/rules +++ b/debian/rules @@ -156,6 +156,10 @@ endif PREFIX=/usr SYSCONFDIR=/etc/fossology LOCALSTATEDIR=/var \ -C src/ninka install + $(MAKE) DESTDIR=$(CURDIR)/debian/fossology-ojo \ + PREFIX=/usr SYSCONFDIR=/etc/fossology LOCALSTATEDIR=/var \ + -C src/ojo install + $(MAKE) DESTDIR=$(CURDIR)/debian/fossology-decider \ PREFIX=/usr SYSCONFDIR=/etc/fossology LOCALSTATEDIR=/var \ -C src/decider install diff --git a/src/Makefile b/src/Makefile index 76b42d2d51..2c60dc54d0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -23,6 +23,7 @@ DIRS = \ monk \ ninka \ nomos \ + ojo \ pkgagent \ readmeoss \ unifiedreport \ diff --git a/src/dox.c b/src/dox.c index 3b3fdb7c33..0a4bd24fcf 100644 --- a/src/dox.c +++ b/src/dox.c @@ -168,6 +168,7 @@ * -# \subpage maintagent * -# \subpage mimetype * -# \subpage nomos + * -# \subpage ojo * -# \subpage pkgagent * -# \subpage readmeoss * -# \subpage reuser diff --git a/src/nomos/agent/json_writer.c b/src/nomos/agent/json_writer.c index 15911ecf3b..956e611323 100644 --- a/src/nomos/agent/json_writer.c +++ b/src/nomos/agent/json_writer.c @@ -20,7 +20,7 @@ #include "json_writer.h" #include "nomos.h" #include "nomos_utils.h" -#include "json-c/json.h" +#include void writeToTemp() { diff --git a/src/ojo/Makefile b/src/ojo/Makefile new file mode 100644 index 0000000000..c83ad87388 --- /dev/null +++ b/src/ojo/Makefile @@ -0,0 +1,50 @@ +# Copyright Siemens AG 2019 +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. + +TOP = ../.. +VARS = $(TOP)/Makefile.conf +include $(VARS) + +MOD_NAME = ojo + +DIRS = agent ui +TESTDIR = + +DIR_LOOP = @set -e; for dir in $(DIRS); do $(MAKE) -s -C $$dir $(1); done + +all: VERSIONFILE ojo-all + $(call DIR_LOOP, ) + +test: all + $(MAKE) -C $(TESTDIR) test + +coverage: all + $(MAKE) -C $(TESTDIR) coverage + +VERSIONFILE: + $(call WriteVERSIONFile,$(MOD_NAME)) + +install: all + $(call DIR_LOOP,install) + $(INSTALL_DATA) VERSION $(DESTDIR)$(MODDIR)/$(MOD_NAME)/VERSION + $(INSTALL_DATA) $(MOD_NAME).conf $(DESTDIR)$(MODDIR)/$(MOD_NAME)/$(MOD_NAME).conf + mkdir -p $(DESTDIR)$(SYSCONFDIR)/mods-enabled + if test ! -e $(DESTDIR)$(SYSCONFDIR)/mods-enabled/$(MOD_NAME); then \ + ln -s $(MODDIR)/$(MOD_NAME) $(DESTDIR)$(SYSCONFDIR)/mods-enabled; \ + fi + +uninstall: + $(call DIR_LOOP,uninstall) + rm -rf $(DESTDIR)$(MODDIR)/$(MOD_NAME) + rm -f $(DESTDIR)$(SYSCONFDIR)/mods-enabled/$(MOD_NAME) + +clean: + $(call DIR_LOOP,clean) + rm -f VERSION + +.PHONY: all test coverage VERSIONFILE install uninstall clean +.PHONY: ojo-all ojo-install ojo-uninstall ojo-clean ojo-Makefile diff --git a/src/ojo/README.md b/src/ojo/README.md new file mode 100644 index 0000000000..7a419a69eb --- /dev/null +++ b/src/ojo/README.md @@ -0,0 +1,8 @@ +Introduction +-------------- + +Ojo license identification agent, identifies the license(s) with the below format +[SPDX-License-Identifier: <"license name">] +from text file under which source file is made available. + + diff --git a/src/ojo/agent/Makefile b/src/ojo/agent/Makefile new file mode 100644 index 0000000000..75645cf2ac --- /dev/null +++ b/src/ojo/agent/Makefile @@ -0,0 +1,69 @@ +# Copyright Siemens AG 2019 +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. + +TOP = ../../.. +VARS = $(TOP)/Makefile.conf +include $(VARS) + +DEF = -DDATADIR='"$(MODDIR)"' +CXXFLAGS_LOCAL = $(FO_CXXFLAGS) -I. -Wall -fopenmp \ + $(shell pkg-config --cflags jsoncpp) + +CXXFLAGS_LINK = $(FO_CXXLDFLAGS) -fopenmp -lboost_regex -lboost_system \ + -lboost_filesystem -lboost_program_options -lstdc++ \ + $(shell pkg-config --libs jsoncpp) + +EXE = ojo + +OBJECTS = OjosDatabaseHandler.o OjoState.o OjoAgent.o OjoUtils.o directoryScan.o ojoregex.o ojos.o +COVERAGE = $(OBJECTS:%.o=%_cov.o) + +all: $(CXXFOLIB) $(EXE) + +$(EXE): $(CXXFOLIB) $(VARS) $(OBJECTS) + $(CXX) $(OBJECTS) $(DEF) $(CXXFLAGS_LINK) -o $@ + +$(EXE)_cov: $(CXXFOLIB) $(VARS) $(COVERAGE) + $(CXX) $(COVERAGE) $(FLAG_COV) $(DEF) $(CXXFLAGS_LINK) -o $@ + +####################### +# library build rules # +####################### + +libojo.a: $(OBJECTS) + ar cvr $@ $(OBJECTS) + +libojo_cov.a: $(COVERAGE) + ar cvr $@ $(COVERAGE) + +$(CXXFOLIB): + $(MAKE) -C $(CXXFOLIBDIR) + +###################### +# object build rules # +###################### + +$(OBJECTS): %.o: %.cc %.hpp + $(CXX) -c $(CXXFLAGS_LOCAL) $(DEF) $< + +$(COVERAGE): %_cov.o: %.cc %.hpp + $(CXX) -c $< $(CXXFLAGS_LOCAL) $(FLAG_COV) $(DEF) $(DEFS) -o $@ + +####################### +# install build rules # +####################### + +install: $(EXE) + $(INSTALL_PROGRAM) $(EXE) $(DESTDIR)$(MODDIR)/$(EXE)/agent/$(EXE) + +uninstall: + rm -rf $(DESTDIR)$(MODDIR)/$(EXE)/agent + +clean: + rm -f $(EXE) *.o *.a *.gcno *.gcda core + +.PHONY: all install uninstall clean diff --git a/src/ojo/agent/OjoAgent.cc b/src/ojo/agent/OjoAgent.cc new file mode 100644 index 0000000000..f934d8f4c3 --- /dev/null +++ b/src/ojo/agent/OjoAgent.cc @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "OjoAgent.hpp" + +using namespace std; + +/** + * Default constructor for OjoAgent. + * + * Also initializes the regex. + */ +OjoAgent::OjoAgent() : + regLicenseList( + boost::regex(SPDX_LICENSE_LIST, boost::regex_constants::icase)), + regLicenseName( + boost::regex(SPDX_LICENSE_NAMES, boost::regex_constants::icase)) +{ +} + +/** + * Scan a single file (when running from scheduler). + * @param filePath The file to be scanned. + * @param databaseHandler Database handler to be used. + * @return List of matches found. + * @sa OjoAgent::scanString() + * @sa OjoAgent::filterMatches() + * @sa OjoAgent::findLicenseId() + * @throws std::runtime_error() Throws runtime error if the file can not be + * read with the file path in description. + */ +vector OjoAgent::processFile(const string &filePath, + OjosDatabaseHandler &databaseHandler) +{ + ifstream stream(filePath); + std::stringstream sstr; + sstr << stream.rdbuf(); + if (stream.fail()) + { + throw std::runtime_error(filePath); + } + stream.close(); + const string fileContent = sstr.str(); + vector licenseList; + vector licenseNames; + + scanString(fileContent, regLicenseList, licenseList, 0); + for (auto m : licenseList) + { + scanString(m.content, regLicenseName, licenseNames, m.start); + } + + findLicenseId(licenseNames, databaseHandler); + filterMatches(licenseNames); + + return licenseNames; +} + +/** + * Scan a single file (when running from CLI). + * + * This function can not interact with DB. + * @param filePath File to be scanned + * @return List of matches. + */ +vector OjoAgent::processFile(const string &filePath) +{ + ifstream stream(filePath); + std::stringstream sstr; + sstr << stream.rdbuf(); + if (stream.fail()) + { + throw std::runtime_error(filePath); + } + stream.close(); + const string fileContent = sstr.str(); + vector licenseList; + vector licenseNames; + + scanString(fileContent, regLicenseList, licenseList, 0); + for (auto m : licenseList) + { + scanString(m.content, regLicenseName, licenseNames, m.start); + } + + return licenseNames; +} + +/** + * Scan a string based using a regex and create matches. + * @param text String to be scanned + * @param reg Regex to be used + * @param[out] result The match list. + * @param offset The offset to be added for each match + */ +void OjoAgent::scanString(const string &text, boost::regex reg, + vector &result, unsigned int offset) +{ + string::const_iterator end = text.end(); + string::const_iterator pos = text.begin(); + + while (pos != end) + { + // Find next match + boost::smatch res; + if (boost::regex_search(pos, end, res, reg)) + { + // Found match + result.push_back( + ojomatch(offset + res.position(1), + offset + res.position(1) + res.length(1), + res.length(1), + res[1].str())); + pos = res[0].second; + offset += res.position() + res.length(); + } + else + { + // No match found + break; + } + } +} + +/** + * Filter the matches list and remove entries with license id less than 1. + * @param[in,out] matches List of matches to be filtered + */ +void OjoAgent::filterMatches(vector &matches) +{ + // Remvoe entries with license_fk < 1 + matches.erase( + std::remove_if(matches.begin(), matches.end(), [](ojomatch match) + { return match.license_fk <= 0;}), matches.end()); +} + +/** + * Update the license id for each match entry + * @param[in,out] matches List of matches to be updated + * @param databaseHandler Database handler to be used + */ +void OjoAgent::findLicenseId(vector &matches, + OjosDatabaseHandler &databaseHandler) +{ + // Update license_fk + for (size_t i = 0; i < matches.size(); ++i) + { + matches[i].license_fk = databaseHandler.getLicenseIdForName( + matches[i].content); + } +} diff --git a/src/ojo/agent/OjoAgent.hpp b/src/ojo/agent/OjoAgent.hpp new file mode 100644 index 0000000000..f8cdcb3b8d --- /dev/null +++ b/src/ojo/agent/OjoAgent.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * @file + * OjoAgent - the regex runner + */ +#ifndef SRC_OJO_AGENT_OJOAGENT_HPP_ +#define SRC_OJO_AGENT_OJOAGENT_HPP_ + +#include +#include + +#include "OjosDatabaseHandler.hpp" +#include "ojomatch.hpp" +#include "ojoregex.hpp" + +/** + * @class OjoAgent + * The OjoAgent class with various functions to scan a file. + */ +class OjoAgent +{ + public: + OjoAgent(); + std::vector processFile(const std::string &filePath, + OjosDatabaseHandler &databaseHandler); + std::vector processFile(const std::string &filePath); + private: + /** + * @var boost::regex regLicenseList + * Regex to find the list of licenses + * @var boost::regex regLicenseName + * Regex to find the license names from the license lists + */ + const boost::regex regLicenseList, regLicenseName; + void scanString(const std::string &text, boost::regex reg, + std::vector &result, unsigned int offset); + void filterMatches(std::vector &matches); + void findLicenseId(std::vector &matches, + OjosDatabaseHandler &databaseHandler); +}; + +#endif /* SRC_OJO_AGENT_OJOAGENT_HPP_ */ diff --git a/src/ojo/agent/OjoState.cc b/src/ojo/agent/OjoState.cc new file mode 100644 index 0000000000..8e3199ba2c --- /dev/null +++ b/src/ojo/agent/OjoState.cc @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "OjoState.hpp" + +/** + * Constructor for State + * @param agentId Agent ID + * @param cliOptions CLI options passed + */ +OjoState::OjoState(const int agentId, const OjoCliOptions &cliOptions) : + agentId(agentId), cliOptions(cliOptions) +{ +} + +/** + * Get the agent id + * @return Agent id + */ +void OjoState::setAgentId(const int agentId) +{ + this->agentId = agentId; +} + +/** + * Get the agent id + * @return Agent id + */ +int OjoState::getAgentId() const +{ + return agentId; +} + +/** + * Get the OjoAgent reference + * @return OjoAgent reference + */ +const OjoAgent& OjoState::getOjoAgent() const +{ + return ojoAgent; +} + +/** + * @brief Constructor for OjoCliOptions + * @param verbosity Verbosity set by CLI + * @param json True to get output in JSON format + */ +OjoCliOptions::OjoCliOptions(int verbosity, bool json) : + verbosity(verbosity), json(json) +{ +} + +/** + * @brief Default constructor for OjoCliOptions + */ +OjoCliOptions::OjoCliOptions() : + verbosity(0), json(false) +{ +} + +/** + * @brief Get the OjoCliOptions set by user + * @return The OjoCliOptions + */ +const OjoCliOptions& OjoState::getCliOptions() const +{ + return cliOptions; +} + +/** + * @brief Check if verbosity is set + * @return True if set, else false + */ +bool OjoCliOptions::isVerbosityDebug() const +{ + return verbosity >= 1; +} + +/** + * @brief Check if JSON output is required + * @return True if required, else false + */ +bool OjoCliOptions::doJsonOutput() const +{ + return json; +} diff --git a/src/ojo/agent/OjoState.hpp b/src/ojo/agent/OjoState.hpp new file mode 100644 index 0000000000..a541c35cbc --- /dev/null +++ b/src/ojo/agent/OjoState.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * @file + * State and CLI options for OJO agent + */ + +#ifndef OJOS_AGENT_STATE_HPP +#define OJOS_AGENT_STATE_HPP + +#include "libfossdbmanagerclass.hpp" +#include "OjoAgent.hpp" + +using namespace std; + +/** + * @class OjoCliOptions + * @brief Store the options sent through the CLI + */ +class OjoCliOptions +{ + private: + int verbosity; /**< The verbosity level */ + bool json; /**< Whether to generate JSON output */ + + public: + bool isVerbosityDebug() const; + bool doJsonOutput() const; + + OjoCliOptions(int verbosity, bool json); + OjoCliOptions(); +}; + +/** + * @class OjoState + * @brief Store the state of the agent + */ +class OjoState +{ + public: + OjoState(const int agentId, const OjoCliOptions &cliOptions); + + void setAgentId(const int agentId); + int getAgentId() const; + const OjoCliOptions& getCliOptions() const; + const OjoAgent& getOjoAgent() const; + + private: + int agentId; /**< Agent id */ + const OjoCliOptions cliOptions; /**< CLI options passed */ + const OjoAgent ojoAgent; /**< Ojo agent object */ +}; + +#endif // OJOS_AGENT_STATE_HPP diff --git a/src/ojo/agent/OjoUtils.cc b/src/ojo/agent/OjoUtils.cc new file mode 100644 index 0000000000..398564966c --- /dev/null +++ b/src/ojo/agent/OjoUtils.cc @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * @file + * The utility functions for OJO agent + */ + +#include + +#include "OjoUtils.hpp" +#include "OjoAgent.hpp" + +using namespace fo; + +/** + * @brief Create a new state for the current agent based on CliOptions. + * + * Called during instantiation of agent. + * @param cliOptions CLI options passed to the agent + * @return New OjoState object for the agent + */ +OjoState getState(DbManager &dbManager, OjoCliOptions &&cliOptions) +{ + int agentId = queryAgentId(dbManager); + return OjoState(agentId, std::move(cliOptions)); +} + +/** + * @brief Create a new state for the agent without DB manager + * @param cliOptions CLI options passed + * @return New OjoState object + */ +OjoState getState(OjoCliOptions &&cliOptions) +{ + return OjoState(-1, std::move(cliOptions)); +} + +/** + * Query the agent ID from the DB. + * @param dbManager DbManager to be used + * @return The agent if found, bail otherwise. + */ +int queryAgentId(DbManager &dbManager) +{ + char* COMMIT_HASH = fo_sysconfig(AGENT_NAME, "COMMIT_HASH"); + char* VERSION = fo_sysconfig(AGENT_NAME, "VERSION"); + char *agentRevision; + + if (!asprintf(&agentRevision, "%s.%s", VERSION, COMMIT_HASH)) + bail(-1); + + int agentId = fo_GetAgentKey(dbManager.getConnection(), AGENT_NAME, 0, + agentRevision, AGENT_DESC); + free(agentRevision); + + if (agentId <= 0) + bail(1); + + return agentId; +} + +/** + * Write ARS to the agent's ars table + * @param state State of the agent + * @param arsId ARS id (0 for new entry) + * @param uploadId Upload ID + * @param success Success status + * @param dbManager DbManager to use + * @return ARS ID. + */ +int writeARS(const OjoState &state, int arsId, int uploadId, int success, + DbManager &dbManager) +{ + PGconn *connection = dbManager.getConnection(); + int agentId = state.getAgentId(); + + return fo_WriteARS(connection, arsId, uploadId, agentId, AGENT_ARS, NULL, + success); +} + +/** + * Disconnect scheduler and exit in case of failure. + * @param exitval Exit code to be sent to scheduler and returned by program + */ +void bail(int exitval) +{ + fo_scheduler_disconnect(exitval); + exit(exitval); +} + +/** + * Process a given upload id + * @param state State of the agent + * @param uploadId Upload ID to be scanned + * @param databaseHandler Database handler to be used + * @return True in case of successful scan, false otherwise. + */ +bool processUploadId(const OjoState &state, int uploadId, + OjosDatabaseHandler &databaseHandler) +{ + vector fileIds = databaseHandler.queryFileIdsForScan( + uploadId, state.getAgentId()); + char const *repoArea = "files"; + + bool errors = false; +#pragma omp parallel + { + OjosDatabaseHandler threadLocalDatabaseHandler(databaseHandler.spawn()); + + size_t pFileCount = fileIds.size(); + OjoAgent agentObj = state.getOjoAgent(); +#pragma omp for + for (size_t it = 0; it < pFileCount; ++it) + { + if (errors) + continue; + + unsigned long pFileId = fileIds[it]; + + if (pFileId == 0) + continue; + + char *fileName = threadLocalDatabaseHandler.getPFileNameForFileId( + pFileId); + char *filePath = NULL; +#pragma omp critical (repo_mk_path) + filePath = fo_RepMkPath(repoArea, fileName); + + if (!filePath) + { + LOG_FATAL( + AGENT_NAME" was unable to derive a file path for pfile %ld. Check your HOSTS configuration.", + pFileId); + errors = true; + } + + vector identified; + try + { + identified = agentObj.processFile(filePath, threadLocalDatabaseHandler); + } + catch (std::runtime_error &e) + { + LOG_FATAL("Unable to read %s.", e.what()); + continue; + } + + if (!storeResultInDb(identified, threadLocalDatabaseHandler, + state.getAgentId(), pFileId)) + { + LOG_FATAL("Unable to store results in database for pfile %ld.", + pFileId); + bail(-20); + } + + fo_scheduler_heart(1); + } + } + + return !errors; +} + +/** + * @brief Store the results from scan to DB. + * + * Store the license finding (if found) and highlight to the database. + * + * Store not found entries for empty matches to the database. + * @param matches List of matches. + * @param databaseHandle Database handler to be used + * @param agent_fk Current agent id + * @param pfile_fk Current pfile id + * @return True on success, false otherwise. + */ +bool storeResultInDb(const vector &matches, + OjosDatabaseHandler &databaseHandle, const int agent_fk, const int pfile_fk) +{ + if (!databaseHandle.begin()) + { + return false; + } + + size_t count = 0; + if (matches.size() == 0) + { + OjoDatabaseEntry entry(-1, agent_fk, pfile_fk); + databaseHandle.insertNoResultInDatabase(entry); + return databaseHandle.commit(); + } + for (auto m : matches) + { + OjoDatabaseEntry entry(m.license_fk, agent_fk, pfile_fk); + + if (entry.license_fk > 0) + { + ++count; + unsigned long int fl_pk = databaseHandle.saveLicenseToDatabase(entry); + if (!(fl_pk > 0) || !databaseHandle.saveHighlightToDatabase(m, fl_pk)) + { + databaseHandle.rollback(); + return false; + } + } + else + { + databaseHandle.insertNoResultInDatabase(entry); + } + } + + return databaseHandle.commit(); +} + +/** + * @brief Parse the options sent by CLI to CliOptions object + * @param[in] argc + * @param[in] argv + * @param[out] dest The parsed OjoCliOptions object + * @param[out] fileNames List of files to be scanned + * @param[out] directoryToScan Path of the directory to be scanned + * @return True if success, false otherwise + */ +bool parseCliOptions(int argc, char **argv, OjoCliOptions &dest, + std::vector &fileNames, string &directoryToScan) +{ + boost::program_options::options_description desc( + AGENT_NAME ": recognized options"); + desc.add_options() + ( + "help,h", "shows help" + ) + ( + "verbose,v", "increase verbosity" + ) + ( + "files", + boost::program_options::value >(), + "files to scan" + ) + ( + "json,J", "output JSON" + ) + ( + "config,c", + boost::program_options::value(), + "path to the sysconfigdir" + ) + ( + "scheduler_start", + "specifies, that the command was called by the scheduler" + ) + ( + "userID", + boost::program_options::value(), + "the id of the user that created the job (only in combination with --scheduler_start)" + ) + ( + "groupID", + boost::program_options::value(), + "the id of the group of the user that created the job (only in combination with --scheduler_start)" + ) + ( + "jobId", + boost::program_options::value(), + "the id of the job (only in combination with --scheduler_start)" + ) + ( + "directory,d", + boost::program_options::value(), + "directory to scan (recursive)" + ) + ; + + boost::program_options::positional_options_description p; + p.add("files", -1); + + boost::program_options::variables_map vm; + + try + { + boost::program_options::store( + boost::program_options::command_line_parser(argc, argv).options(desc).positional( + p).run(), vm); + + if (vm.count("help") > 0) + { + cout << desc << endl; + exit(0); + } + + if (vm.count("files")) + { + fileNames = vm["files"].as >(); + } + + unsigned long verbosity = vm.count("verbose"); + bool json = vm.count("json") > 0 ? true : false; + + dest = OjoCliOptions(verbosity, json); + + if (vm.count("directory")) + { + if (vm.count("files")) + { + cout << "cannot pass files and directory at the same time" << endl; + cout << desc << endl; + fileNames.clear(); + return false; + } + directoryToScan = vm["directory"].as(); + } + + return true; + } + catch (boost::bad_any_cast&) + { + cout << "wrong parameter type" << endl; + cout << desc << endl; + return false; + } + catch (boost::program_options::error&) + { + cout << "wrong command line arguments" << endl; + cout << desc << endl; + return false; + } +} + +/** + * Append a new result from scanner to STDOUT + * @param fileName File which was scanned + * @param resultPair The result pair from scanSingleFile() + * @param printComma Set true to print comma. Will be set true after first + * data is printed + */ +void appendToJson(const std::string fileName, + const std::pair> resultPair, + bool &printComma) +{ + Json::Value result; + Json::FastWriter jsonBuilder; + jsonBuilder.omitEndingLineFeed(); + if (resultPair.first.empty()) + { + result["file"] = fileName; + result["results"] = "Unable to read file"; + } + else + { + vector resultList = resultPair.second; + Json::Value results; + for (auto m : resultList) + { + Json::Value j; + j["start"] = Json::Value::UInt(m.start); + j["end"] = Json::Value::UInt(m.end); + j["len"] = Json::Value::UInt(m.len); + j["license"] = m.content; + results.append(j); + } + result["file"] = fileName; + result["results"] = results; + } + // Thread-Safety: output all matches JSON at once to STDOUT +#pragma omp critical (jsonPrinter) + { + if (printComma) + { + cout << "," << endl; + } + else + { + printComma = true; + } + cout << " " << jsonBuilder.write(result) << flush; + } +} + +/** + * Print the result of current scan to stdout + * @param fileName File which was scanned + * @param resultPair Result pair from scanSingleFile() + */ +void printResultToStdout(const std::string fileName, + const std::pair> resultPair) +{ + if (resultPair.first.empty()) + { + cout << fileName << " :: Unable to read file" << endl; + return; + } + stringstream ss; + ss << fileName << " ::" << endl; + // Output matches + vector resultList = resultPair.second; + for (auto m : resultList) + { + ss << "\t[" << m.start << ':' << m.end << "]: '" << m.content << "'" << endl; + } + // Thread-Safety: output all matches (collected in ss) at once to cout + cout << ss.str(); +} diff --git a/src/ojo/agent/OjoUtils.hpp b/src/ojo/agent/OjoUtils.hpp new file mode 100644 index 0000000000..9c92d1c351 --- /dev/null +++ b/src/ojo/agent/OjoUtils.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef OJOS_AGENT_UTILS_HPP +#define OJOS_AGENT_UTILS_HPP + +#define AGENT_NAME "ojo" +#define AGENT_DESC "ojo agent" +#define AGENT_ARS "ojo_ars" + +#include +#include +#include +#include + +#include "ojomatch.hpp" +#include "OjoState.hpp" +#include "libfossologyCPP.hpp" +#include "OjosDatabaseHandler.hpp" + +extern "C" { +#include "libfossology.h" +} + +using namespace std; + +OjoState getState(fo::DbManager &dbManager, OjoCliOptions &&cliOptions); +OjoState getState(OjoCliOptions &&cliOptions); +int queryAgentId(fo::DbManager &dbManager); +int writeARS(const OjoState &state, int arsId, int uploadId, int success, + fo::DbManager &dbManager); +void bail(int exitval); +bool processUploadId(const OjoState &state, int uploadId, + OjosDatabaseHandler &databaseHandler); +bool storeResultInDb(const vector &matches, + OjosDatabaseHandler &databaseHandle, const int agent_fk, + const int pfile_fk); +bool parseCliOptions(int argc, char **argv, OjoCliOptions &dest, + std::vector &fileNames, std::string &directoryToScan); +void appendToJson(const std::string fileName, + const pair> resultPair, bool &printComma); +void printResultToStdout(const std::string fileName, + const pair> resultPair); + +#endif // OJOS_AGENT_UTILS_HPP diff --git a/src/ojo/agent/OjosDatabaseHandler.cc b/src/ojo/agent/OjosDatabaseHandler.cc new file mode 100644 index 0000000000..20f6ca95f6 --- /dev/null +++ b/src/ojo/agent/OjosDatabaseHandler.cc @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * @file + * @brief Data base handler for OJO + */ + +#include "OjosDatabaseHandler.hpp" + +using namespace fo; +using namespace std; + +/** + * Default constructor for OjosDatabaseHandler + * @param dbManager DBManager to be used + */ +OjosDatabaseHandler::OjosDatabaseHandler(DbManager dbManager) : + fo::AgentDatabaseHandler(dbManager) +{ +} + +/** + * Get a vector of all file id for a given upload id. + * @param uploadId Upload ID to be queried + * @return List of all pfiles for the given upload + */ +vector OjosDatabaseHandler::queryFileIdsForUpload(int uploadId) +{ + return queryFileIdsVectorForUpload(uploadId); +} + +/** + * Get a vector of all file id for a given upload id which are not scanned by the given agentId. + * @param uploadId Upload ID to be queried + * @param agentId ID of the agent + * @return List of all pfiles for the given upload + */ +vector OjosDatabaseHandler::queryFileIdsForScan(int uploadId, int agentId) +{ + string uploadtreeTableName = queryUploadTreeTableName(uploadId); + + QueryResult queryResult = dbManager.execPrepared( + fo_dbManager_PrepareStamement(dbManager.getStruct_dbManager(), + ("pfileForUploadFilterAgent" + uploadtreeTableName).c_str(), + ("SELECT distinct(ut.pfile_fk) FROM " + uploadtreeTableName + " AS ut " + "LEFT JOIN license_file AS lf ON ut.pfile_fk = lf.pfile_fk " + "AND lf.agent_fk = $2 WHERE lf.pfile_fk IS NULL " + "AND ut.upload_fk = $1 AND (ut.ufile_mode&x'3C000000'::int)=0;").c_str(), + int, int), + uploadId, agentId); + + return queryResult.getSimpleResults(0, fo::stringToUnsignedLong); +} + +/** + * Spawn a new DbManager object. + * + * Used to create new objects for threads. + * @return DbManager object for threads. + */ +OjosDatabaseHandler OjosDatabaseHandler::spawn() const +{ + DbManager spawnedDbMan(dbManager.spawn()); + return OjosDatabaseHandler(spawnedDbMan); +} + +/** + * @brief Save findings to the database if agent was called by scheduler + * @param entry The entry to be made + * @return fl_pk on success, -1 on failure + */ +unsigned long OjosDatabaseHandler::saveLicenseToDatabase( + OjoDatabaseEntry &entry) const +{ + QueryResult queryResult = dbManager.execPrepared( + fo_dbManager_PrepareStamement(dbManager.getStruct_dbManager(), + "ojoInsertLicense", + "INSERT INTO license_file" + "(rf_fk, agent_fk, pfile_fk)" + " VALUES($1,$2,$3) RETURNING fl_pk", + long, long, long), + entry.license_fk, entry.agent_fk, entry.pfile_fk); + vector res = queryResult.getSimpleResults(0, + fo::stringToUnsignedLong); + if (res.size() > 0) + { + return res.at(0); + } + else + { + return -1; + } +} + +/** + * Save findings highlights to DB + * @param match Match to be saved + * @param fl_fk fl_pk from license_file table + * @return True on success, false otherwise + */ +bool OjosDatabaseHandler::saveHighlightToDatabase(const ojomatch &match, + const unsigned long fl_fk) const +{ + if (fl_fk < 1) + { + return false; + } + return dbManager.execPrepared( + fo_dbManager_PrepareStamement(dbManager.getStruct_dbManager(), + "ojoInsertHighlight", + "INSERT INTO highlight" + "(fl_fk, start, len, type)" + " VALUES($1,$2,$3,'L')", + long, long, long + ), + fl_fk, match.start, + match.len); +} + +/** + * @brief Save no result to the database + * @param entry Entry containing the agent id and file id + * @return True of successful insertion, false otherwise + */ +bool OjosDatabaseHandler::insertNoResultInDatabase( + OjoDatabaseEntry &entry) const +{ + return dbManager.execPrepared( + fo_dbManager_PrepareStamement(dbManager.getStruct_dbManager(), + "ojoInsertNoLicense", + "INSERT INTO license_file" + "(agent_fk, pfile_fk)" + " VALUES($1,$2)", + long, long + ), + entry.agent_fk, entry.pfile_fk); +} + +/** + * Helper function to check if a string ends with other string. + * @param firstString The string to be checked + * @param ending The ending string + * @return True if first string has the ending string at end, false otherwise. + */ +bool hasEnding(string const &firstString, string const &ending) +{ + if (firstString.length() >= ending.length()) + { + return (0 + == firstString.compare(firstString.length() - ending.length(), + ending.length(), ending)); + } + else + { + return false; + } +} + +/** + * Get the license id for a given short name or create a new entry. + * + * @note + * The following rules are also applied on name matching: + * -# All matches are case in-sensitive. + * -# `GPL-2.0 and GPL-2.0-only` are treated as same + * -# `GPL-2.0+ and GPL-2.0-or-later` are treated as same + * + * @param rfShortName Short name to be searched. + * @returns License id, 0 on failure + */ +unsigned long OjosDatabaseHandler::selectOrInsertLicenseIdForName( + string rfShortName) +{ + bool success = false; + unsigned long result = 0; + + fo_dbManager_PreparedStatement *searchWithOr = fo_dbManager_PrepareStamement( + dbManager.getStruct_dbManager(), + "selectLicenseIdWithOrOJO", + "SELECT rf_pk FROM ONLY license_ref" + " WHERE LOWER(rf_shortname) = LOWER($1)" + " OR LOWER(rf_shortname) = LOWER($2)" + " ORDER BY rf_pk ASC;", + char*, char*); + + /* First check similar matches */ + /* Check if the name ends with +, -or-later, -only */ + if (hasEnding(rfShortName, "+") || hasEnding(rfShortName, "-or-later")) + { + string tempShortName(rfShortName); + /* Convert shortname to lower-case */ + std::transform(tempShortName.begin(), tempShortName.end(), tempShortName.begin(), + ::tolower); + string plus("+"); + string orLater("-or-later"); + + unsigned long int plusLast = tempShortName.rfind(plus); + unsigned long int orLaterLast = tempShortName.rfind(orLater); + + /* Remove last occurrence of + and -or-later (if found) */ + if (plusLast != string::npos) + { + tempShortName.replace(plusLast, plus.length(), ""); + } + if (orLaterLast != string::npos) + { + tempShortName.replace(orLaterLast, orLater.length(), ""); + } + + QueryResult queryResult = dbManager.execPrepared(searchWithOr, + (tempShortName + plus).c_str(), (tempShortName + orLater).c_str()); + + success = queryResult && queryResult.getRowCount() > 0; + if (success) + { + result = queryResult.getSimpleResults(0, fo::stringToUnsignedLong)[0]; + } + } + else + { + string tempShortName(rfShortName); + /* Convert shortname to lower-case */ + std::transform(tempShortName.begin(), tempShortName.end(), tempShortName.begin(), + ::tolower); + string only("-only"); + + unsigned long int onlyLast = tempShortName.rfind(only); + + /* Remove last occurrence of -only (if found) */ + if (onlyLast != string::npos) + { + tempShortName.replace(onlyLast, only.length(), ""); + } + + QueryResult queryResult = dbManager.execPrepared(searchWithOr, + tempShortName.c_str(), (tempShortName + only).c_str()); + + success = queryResult && queryResult.getRowCount() > 0; + if (success) + { + result = queryResult.getSimpleResults(0, fo::stringToUnsignedLong)[0]; + } + } + + if (result > 0) + { + return result; + } + + unsigned count = 0; + while ((!success) && count++ < 3) + { + if (!dbManager.begin()) + continue; + + dbManager.queryPrintf("LOCK TABLE license_ref"); + + QueryResult queryResult = + dbManager.execPrepared( + fo_dbManager_PrepareStamement(dbManager.getStruct_dbManager(), + "selectOrInsertLicenseIdForNameOjo", + "WITH " + "selectExisting AS (" + "SELECT rf_pk FROM ONLY license_ref" + " WHERE LOWER(rf_shortname) = LOWER($1)" + ")," + "insertNew AS (" + "INSERT INTO license_ref(rf_shortname, rf_text, rf_detector_type)" + " SELECT $1, $2, $3" + " WHERE NOT EXISTS(SELECT * FROM selectExisting)" + " RETURNING rf_pk" + ") " + + "SELECT rf_pk FROM insertNew " + "UNION " + "SELECT rf_pk FROM selectExisting", + char*, char*, int + ), + rfShortName.c_str(), "License by OJO.", 3); + + success = queryResult && queryResult.getRowCount() > 0; + + if (success) + { + success &= dbManager.commit(); + + if (success) + { + result = queryResult.getSimpleResults(0, fo::stringToUnsignedLong)[0]; + } + } + else + { + dbManager.rollback(); + } + } + + return result; +} + +/** + * @brief Get the license id for a given short name. + * + * The function first checks if the license exists in the cache list. If the + * license is not cached, it checks in DB and store in the cache. + * @param rfShortName Short name to be searched + * @returns License ID if found, 0 otherwise + * @sa OjosDatabaseHandler::getCachedLicenseIdForName() + */ +unsigned long OjosDatabaseHandler::getLicenseIdForName( + string const &rfShortName) +{ + unsigned long licenseId = getCachedLicenseIdForName(rfShortName); + if (licenseId == 0) + { + licenseId = selectOrInsertLicenseIdForName(rfShortName); + licenseRefCache.insert(std::make_pair(rfShortName, licenseId)); + } + return licenseId; +} + +/** + * Get the license id from the cached license list. + * @param rfShortName Name of the license + * @returns License id if found, 0 otherwise + */ +unsigned long OjosDatabaseHandler::getCachedLicenseIdForName( + string const &rfShortName) const +{ + std::unordered_map::const_iterator findIterator = + licenseRefCache.find(rfShortName); + if (findIterator != licenseRefCache.end()) + { + return findIterator->second; + } + else + { + return 0; + } +} diff --git a/src/ojo/agent/OjosDatabaseHandler.hpp b/src/ojo/agent/OjosDatabaseHandler.hpp new file mode 100644 index 0000000000..2496c738a1 --- /dev/null +++ b/src/ojo/agent/OjosDatabaseHandler.hpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * @file + * @brief Database handler for OJO + */ + +#ifndef OJOS_AGENT_DATABASE_HANDLER_HPP +#define OJOS_AGENT_DATABASE_HANDLER_HPP + +#include +#include +#include +#include + +#include "libfossUtils.hpp" +#include "libfossAgentDatabaseHandler.hpp" +#include "libfossdbmanagerclass.hpp" +#include "ojomatch.hpp" + +extern "C" { +#include "libfossology.h" +} + +/** + * @struct OjoDatabaseEntry + * Structure to hold entries to be inserted in DB + */ +struct OjoDatabaseEntry +{ + /** + * @var long int license_fk + * License ID + * @var long int agent_fk + * Agent ID + * @var long int pfile_fk + * Pfile ID + */ + const unsigned long int license_fk, agent_fk, pfile_fk; + /** + * Constructor for OjoDatabaseEntry structure + * @param l License ID + * @param a Agent ID + * @param p Pfile ID + */ + OjoDatabaseEntry(const unsigned long int l, const unsigned long int a, + const unsigned long int p) : + license_fk(l), agent_fk(a), pfile_fk(p) + { + } +}; + +/** + * @class OjosDatabaseHandler + * Database handler for OJO agent + */ +class OjosDatabaseHandler: public fo::AgentDatabaseHandler +{ + public: + OjosDatabaseHandler(fo::DbManager dbManager); + OjosDatabaseHandler(OjosDatabaseHandler &&other) : + fo::AgentDatabaseHandler(std::move(other)) + { + } + ; + OjosDatabaseHandler spawn() const; + + std::vector queryFileIdsForUpload(int uploadId); + std::vector queryFileIdsForScan(int uploadId, int agentId); + unsigned long saveLicenseToDatabase(OjoDatabaseEntry &entry) const; + bool insertNoResultInDatabase(OjoDatabaseEntry &entry) const; + bool saveHighlightToDatabase(const ojomatch &match, + const unsigned long fl_fk) const; + + unsigned long getLicenseIdForName(std::string const &rfShortName); + + private: + unsigned long getCachedLicenseIdForName( + std::string const &rfShortName) const; + unsigned long selectOrInsertLicenseIdForName(std::string rfShortname); + /** + * Cached license pairs + */ + std::unordered_map licenseRefCache; +}; + +#endif // OJOS_AGENT_DATABASE_HANDLER_HPP diff --git a/src/ojo/agent/directoryScan.cc b/src/ojo/agent/directoryScan.cc new file mode 100644 index 0000000000..de6ffed853 --- /dev/null +++ b/src/ojo/agent/directoryScan.cc @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * \file + * \brief Utilities to scan directories + */ + +#include "directoryScan.hpp" + +using namespace std; +namespace fs = boost::filesystem; + +/** + * Scan a given directory with OJO and print as JSON or to STDOUT based on + * json parameter. + * @param json Set true to print results as JSON, false to print as plain text + * @param directoryPath Directory to be scanned. + */ +void scanDirectory(const bool json, const string &directoryPath) +{ + fs::recursive_directory_iterator dirIterator(directoryPath); + fs::recursive_directory_iterator end; + + OjoAgent agentObj; + + vector filePaths; + + for (fs::path const &p : dirIterator) + { + if (fs::is_directory(p)) + { + // Can not do anything with a directory + continue; + } + // Store the paths in a vector as of now since we can not `#pragma omp for` + // on recursive_directory_iterator + filePaths.push_back(p.string()); + } + const unsigned long filePathsSize = filePaths.size(); + bool printComma = false; + + if (json) + { + cout << "[" << endl; + } +#pragma omp parallel shared(printComma) + { +#pragma omp for + for (unsigned int i = 0; i < filePathsSize; i++) + { + const string fileName = filePaths[i]; + + vector l; + try + { + l = agentObj.processFile(fileName); + } + catch (std::runtime_error &e) + { + cerr << "Unable to read " << e.what(); + continue; + } + pair> scanResult(fileName, l); + if (json) + { + appendToJson(fileName, scanResult, printComma); + } + else + { + printResultToStdout(fileName, scanResult); + } + } + } + if (json) + { + cout << endl << "]" << endl; + } +} diff --git a/src/ojo/agent/directoryScan.hpp b/src/ojo/agent/directoryScan.hpp new file mode 100644 index 0000000000..bd6649426f --- /dev/null +++ b/src/ojo/agent/directoryScan.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SRC_OJO_AGENT_DIRECTORYSCAN_HPP_ +#define SRC_OJO_AGENT_DIRECTORYSCAN_HPP_ + +#include +#include + +#include "OjoUtils.hpp" +#include "OjoAgent.hpp" + +void scanDirectory(const bool json, const std::string &directoryPath); + +#endif /* SRC_OJO_AGENT_DIRECTORYSCAN_HPP_ */ diff --git a/src/ojo/agent/ojomatch.hpp b/src/ojo/agent/ojomatch.hpp new file mode 100644 index 0000000000..db3cfa2ebe --- /dev/null +++ b/src/ojo/agent/ojomatch.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SRC_OJO_AGENT_OJOMATCH_HPP_ +#define SRC_OJO_AGENT_OJOMATCH_HPP_ + +#include + +/** + * @struct ojomatch + * @brief Store the results of a regex match + */ +struct ojomatch +{ + /** + * @var long int start + * Start position of match + * @var long int end + * End position of match + * @var long int len + * Length of match + * @var long int license_fk + * License ref of the match if found + */ + long int start, end, len, license_fk; + + /** + * @var + * Matched string + */ + std::string content; + /** + * Constructor for ojomatch structure + * @param s Start of match + * @param e End of match + * @param l Length of match + * @param c Content of match + */ + ojomatch(const long int s, const long int e, const long int l, + const std::string c) : + start(s), end(e), len(l), content(c) + { + license_fk = -1; + } + + bool operator==(const std::string& matchcontent) const + { + if(this->content.compare(matchcontent) == 0) + { + return true; + } + else + { + return false; + } + } +}; + +#endif /* SRC_OJO_AGENT_OJOMATCH_HPP_ */ diff --git a/src/ojo/agent/ojoregex.cc b/src/ojo/agent/ojoregex.cc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ojo/agent/ojoregex.hpp b/src/ojo/agent/ojoregex.hpp new file mode 100644 index 0000000000..6644a4a8ab --- /dev/null +++ b/src/ojo/agent/ojoregex.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * @file + * The list of regex used in the agent. + * + * Each regex is stored as a macro. + */ +#ifndef SRC_OJO_AGENT_OJOREGEX_HPP_ +#define SRC_OJO_AGENT_OJOREGEX_HPP_ + +/** + * @def SPDX_LICENSE_LIST + * @brief Regex to filter the list of licenses. + * + * -# The regex first finds occurance of `spdx-license-identifier` in the text + * -# Throw the text `spdx-license-identifier` + * -# Matches at most 5 identifiers each with max length of 37 (based on + * https://github.com/spdx/license-list-data/tree/master/html) + */ +#define SPDX_LICENSE_LIST "spdx-licen[cs]e(?:id|[- ]identifier): \\K((?:(?: (?:and|or|with) )?\\(?(?:[\\w\\d\\.\\+\\-]{1,37})\\)?){1,5})" +/** + * @def SPDX_LICENSE_NAMES + * @brief Regex to filter license names from list of license list + * + * -# License names will consist of words, digits, dots and hyphens. + * -# Maximum length of license name will be 37 (based on + * https://github.com/spdx/license-list-data/tree/master/html) + */ +#define SPDX_LICENSE_NAMES "(?: and | or | with )?\\(?([\\w\\d\\.\\+\\-]{1,37})\\)?" + +#endif /* SRC_OJO_AGENT_OJOREGEX_HPP_ */ diff --git a/src/ojo/agent/ojos.cc b/src/ojo/agent/ojos.cc new file mode 100644 index 0000000000..3d5504aace --- /dev/null +++ b/src/ojo/agent/ojos.cc @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * @dir + * @brief The OJO agent + * @file + * @brief Entry point for ojo agent + * @page ojo OJO Agent + * @tableofcontents + * + * OJO agent uses regular expressions to find out SPDX License identifiers + * from a file. + * + * The agent runs in multi-threaded mode and creates a new thread for + * every pfile for faster processing. + * + * @section ojoactions Supported actions + * | Command line flag | Description | + * | ---: | :--- | + * | -h [--help] | Shows help | + * | -v [--verbose] | Increase verbosity | + * | --files arg | Files to scan | + * | -J [--json] | Output JSON | + * | -d [--directory] | Directory to be scanned (recursive) | + * | -c [ --config ] arg | Path to the sysconfigdir | + * | --scheduler_start | Specifies, that the command was called by the | + * || scheduler | + * | --userID arg | The id of the user that created the job (only in | + * || combination with --scheduler_start) | + * | --groupID arg | The id of the group of the user that created the job | + * || (only in combination with --scheduler_start) | + * | --jobId arg | The id of the job (only in combination with | + * || --scheduler_start) | + * @section ojosource Agent source + * - @link src/ojo/agent @endlink + * - @link src/ojo/ui @endlink + */ + +#include "ojos.hpp" + +using namespace fo; + +/** + * @def return_sched(retval) + * Send disconnect to scheduler with retval and return function with retval. + */ +#define return_sched(retval) \ + do {\ + fo_scheduler_disconnect((retval));\ + return (retval);\ + } while(0) + +int main(int argc, char **argv) +{ + OjoCliOptions cliOptions; + vector fileNames; + string directoryToScan; + if (!parseCliOptions(argc, argv, cliOptions, fileNames, directoryToScan)) + { + return_sched(1); + } + + bool json = cliOptions.doJsonOutput(); + OjoState state = getState(std::move(cliOptions)); + + if (!fileNames.empty()) + { + const unsigned long fileNamesCount = fileNames.size(); + bool fileError = false; + bool printComma = false; + OjoAgent agentObj = state.getOjoAgent(); + + if (json) + { + cout << "[" << endl; + } + +#pragma omp parallel shared(printComma) + { +#pragma omp for + for (unsigned int argn = 0; argn < fileNamesCount; ++argn) + { + const string fileName = fileNames[argn]; + + vector l; + try + { + l = agentObj.processFile(fileName); + } + catch (std::runtime_error &e) + { + cerr << "Unable to read " << e.what(); + fileError = true; + continue; + } + pair> scanResult(fileName, l); + if (json) + { + appendToJson(fileName, scanResult, printComma); + } + else + { + printResultToStdout(fileName, scanResult); + } + } + } + if (json) + { + cout << endl << "]" << endl; + } + return fileError ? 1 : 0; + } + else if (directoryToScan.length() > 0) + { + scanDirectory(json, directoryToScan); + } + else + { + DbManager dbManager(&argc, argv); + OjosDatabaseHandler databaseHandler(dbManager); + + state.setAgentId(queryAgentId(dbManager)); + + while (fo_scheduler_next() != NULL) + { + int uploadId = atoi(fo_scheduler_current()); + + if (uploadId == 0) + continue; + + int arsId = writeARS(state, 0, uploadId, 0, dbManager); + + if (arsId <= 0) + bail(5); + + if (!processUploadId(state, uploadId, databaseHandler)) + bail(2); + + fo_scheduler_heart(0); + writeARS(state, arsId, uploadId, 1, dbManager); + } + fo_scheduler_heart(0); + + /* do not use bail, as it would prevent the destructors from running */ + fo_scheduler_disconnect(0); + } + return 0; +} diff --git a/src/ojo/agent/ojos.hpp b/src/ojo/agent/ojos.hpp new file mode 100644 index 0000000000..a329256ef2 --- /dev/null +++ b/src/ojo/agent/ojos.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019, Siemens AG + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef OJOS_AGENT_OJOS_HPP +#define OJOS_AGENT_OJOS_HPP + +#include + +#include "OjoAgent.hpp" +#include "OjoUtils.hpp" +#include "directoryScan.hpp" + +extern "C" { +#include "libfossagent.h" +} + +#endif // OJOS_AGENT_OJOS_HPP diff --git a/src/ojo/mod_deps b/src/ojo/mod_deps new file mode 100755 index 0000000000..ce7eef2ef3 --- /dev/null +++ b/src/ojo/mod_deps @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# FOSSology mod_deps script +# This script helps you install dependencies on a system. for a module +# +# Copyright (C) 2018 Siemens AG +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +source "$(dirname ${BASH_SOURCE[0]})/../../utils/utils.sh" + +# +# Don't show the -y option. Should only be used for install testing, as using +# it without being careful can destroy your system. +# +YesOpt='' + +EVERYTHING='' +RUNTIME='' +BUILDTIME='' + +## Options parsing and setup +# parse options +OPTS=$(getopt -o rbehy --long runtime,buildtime,everything,help -n 'mod_deps' -- "$@") + +if [[ $? -ne 0 ]]; then + OPTS='--help' +fi + +eval set -- "$OPTS" + +# if no options or just -y then do everything +if [[ $OPTS == ' --' || $OPTS == ' -y --' ]]; then + EVERYTHING=true +fi + +while true; do + case "$1" in + -r|--runtime) RUNTIME=true; shift;; + -b|--buildtime) BUILDTIME=true; shift;; + -e|--everything) EVERYTHING=true; shift;; + -y) YesOpt='-y'; shift;; + -h|--help) show_help_for_mod_deps; exit;; + --) shift; break;; + *) echo "ERROR: option $1 not recognised"; exit 1;; + esac +done + +set -o errexit -o nounset -o pipefail + +must_run_as_root +need_lsb_release + +if [[ $EVERYTHING ]]; then + echo "*** Installing both runtime and buildtime dependencies ***" + RUNTIME=true + BUILDTIME=true +fi + +# figure out what distro we're on +DISTRO=$(lsb_release --id --short) +CODENAME=$(lsb_release --codename --short) + +######################################################################## + +if [[ $BUILDTIME ]]; then + echo "*** Installing $DISTRO buildtime dependencies ***"; + case "$DISTRO" in + Debian|Ubuntu) + apt-get $YesOpt install \ + libjsoncpp-dev libboost-system-dev libboost-filesystem-dev \ + libboost-program-options-dev libboost-regex-dev + ;; + Fedora) + yum $YesOpt install \ + jsoncpp-devel boost-devel + ;; + RedHatEnterprise*|CentOS) + yum $YesOpt install epel-release; + yum $YesOpt install \ + jsoncpp-devel boost-devel + ;; + *) echo "ERROR: Unknown or Unsupported $DISTRO $CODENAME release, please report to the mailing list"; exit 1;; + esac +fi + +if [[ $RUNTIME ]]; then + echo "*** Installing $DISTRO runtime dependencies ***"; + case "$DISTRO" in + Debian|Ubuntu) + case "$CODENAME" in + jessie) + apt-get $YesOpt install libjsoncpp0 libboost-filesystem1.55.0 libboost-program-options1.55.0 libboost-regex1.55.0;; + stretch) + apt-get $YesOpt install libjsoncpp1 libboost-filesystem1.62.0 libboost-program-options1.62.0 libboost-regex1.62.0;; + buster|sid) + apt-get $YesOpt install libjsoncpp1 libboost-filesystem1.67.0 libboost-program-options1.67.0 libboost-regex1.67.0;; + xenial) + apt-get $YesOpt install libjsoncpp1 libboost-filesystem1.58.0 libboost-program-options1.58.0 libboost-regex1.58.0;; + bionic) + apt-get $YesOpt install libjsoncpp1 libboost-filesystem1.65.1 libboost-program-options1.65.1 libboost-regex1.65.1;; + *) echo "ERROR: Unknown or Unsupported $DISTRO $CODENAME release, please report to the mailing list"; exit 1;; + esac;; + Fedora) + yum $YesOpt install \ + jsoncpp boost + ;; + RedHatEnterprise*|CentOS) + yum $YesOpt install epel-release; + yum $YesOpt install \ + jsoncpp boost + ;; + *) echo "ERROR: Unknown or Unsupported $DISTRO $CODENAME release, please report to the mailing list"; exit 1;; + esac +fi + +####################################################################### diff --git a/src/ojo/ojo.conf b/src/ojo/ojo.conf new file mode 100644 index 0000000000..b57a8605db --- /dev/null +++ b/src/ojo/ojo.conf @@ -0,0 +1,24 @@ +; Copying and distribution of this file, with or without modification, +; are permitted in any medium without royalty provided the copyright +; notice and this notice are preserved. This file is offered as-is, +; without any warranty. + +; scheduler configure file for this agent +[default] + +; name: The name of the agent from the agent table +name = ojo + +; command: The command that the scheduler will use when creating an instance of this agent. +; This will be parsed like a normal Unix command line. +command = ojo + +; max: The maximum number of this agent that is allowed to exist at any one time. +; This is set to -1 if there is no limit on the number of instances of the agent. +max = -1 + +; special: Scheduler directive for special agent attributes. +; A comma separated list of values. +; Directives: +; EXCLUSIVE: the agent cannot run concurrently with any other agent. +special[] = diff --git a/src/ojo/ui/Makefile b/src/ojo/ui/Makefile new file mode 100644 index 0000000000..ba14d3b8cb --- /dev/null +++ b/src/ojo/ui/Makefile @@ -0,0 +1,28 @@ +# Copyright Siemens AG 2019 +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. + +TOP = ../../.. +VARS = $(TOP)/Makefile.conf +include $(VARS) + +MOD_NAME = ojo +MOD_SUBDIR = ui + +all: + @echo "nothing to do" + +install: + mkdir -p $(DESTDIR)$(MODDIR)/$(MOD_NAME)/$(MOD_SUBDIR) + $(INSTALL_DATA) ./*.php $(DESTDIR)$(MODDIR)/$(MOD_NAME)/$(MOD_SUBDIR) + +uninstall: + rm -rf $(DESTDIR)$(MODDIR)/$(MOD_NAME)/$(MOD_SUBDIR) + +clean: + @echo "nothing to do" + +.PHONY: all install uninstall clean diff --git a/src/ojo/ui/agent-ojos.php b/src/ojo/ui/agent-ojos.php new file mode 100644 index 0000000000..cba182defb --- /dev/null +++ b/src/ojo/ui/agent-ojos.php @@ -0,0 +1,39 @@ +Name = "agent_ojo"; + $this->Title = _("Ojo License Analysis, scanning for licenses using SPDX-License-Identifier"); + $this->AgentName = "ojo"; + + parent::__construct(); + } + + function AgentHasResults($uploadId=0) + { + return CheckARS($uploadId, $this->AgentName, "ojo agent", "ojo_ars"); + } + +} +register_plugin(new OjosAgentPlugin()); diff --git a/src/www/ui/async/AjaxExplorer.php b/src/www/ui/async/AjaxExplorer.php index 7a9a7fcc25..0f7ca1cebc 100644 --- a/src/www/ui/async/AjaxExplorer.php +++ b/src/www/ui/async/AjaxExplorer.php @@ -64,7 +64,7 @@ class AjaxExplorer extends DefaultPlugin /** @var array [uploadtree_id]=>cnt */ private $filesToBeCleared; /** @var array */ - protected $agentNames = array('nomos' => 'N', 'monk' => 'M', 'ninka' => 'Nk', 'reportImport' => 'I'); + protected $agentNames = array('nomos' => 'N', 'monk' => 'M', 'ninka' => 'Nk', 'reportImport' => 'I', 'ojo' => 'O'); public function __construct() { diff --git a/src/www/ui/async/AjaxFileBrowser.php b/src/www/ui/async/AjaxFileBrowser.php index 180afdd127..c68386ea64 100644 --- a/src/www/ui/async/AjaxFileBrowser.php +++ b/src/www/ui/async/AjaxFileBrowser.php @@ -52,7 +52,7 @@ class AjaxFileBrowser extends DefaultPlugin /** @var LicenseMap */ private $licenseProjector; /** @var array */ - protected $agentNames = array('nomos' => 'N', 'monk' => 'M', 'ninka' => 'Nk', 'reportImport' => 'I'); + protected $agentNames = array('nomos' => 'N', 'monk' => 'M', 'ninka' => 'Nk', 'reportImport' => 'I', 'ojo' => 'O'); public function __construct() { diff --git a/src/www/ui/template/browse_file.js.twig b/src/www/ui/template/browse_file.js.twig index 2e322500d7..e3c1993a93 100644 --- a/src/www/ui/template/browse_file.js.twig +++ b/src/www/ui/template/browse_file.js.twig @@ -27,7 +27,7 @@ function createDirlistTable() { } }, "aoColumns": [{"sTitle":"Files","sClass":"left"}, - {"sTitle":"Scanner Results (N: nomos, M: monk, Nk: ninka, I: reportImport)","sClass":"left","bSortable":false}, + {"sTitle":"Scanner Results (N: nomos, M: monk, Nk: ninka, I: reportImport, O: ojo)","sClass":"left","bSortable":false}, {"sTitle":"Edited Results","sClass":"left","bSortable":false}, {"sTitle":"Clearing Status","sClass":"clearingStatus center","bSortable":false,"bSearchable":false,"sWidth":"5%","mRender":function ( data, type, full ) { if(type!='display') return data; diff --git a/src/www/ui/template/file-browse.js.twig b/src/www/ui/template/file-browse.js.twig index 59b5a105d7..b335c37f14 100644 --- a/src/www/ui/template/file-browse.js.twig +++ b/src/www/ui/template/file-browse.js.twig @@ -25,7 +25,7 @@ function createDirlistTable() { } }, "aoColumns": [{"sTitle":"Files","sClass":"left"}, - {"sTitle":"Scanner Results (N: nomos, M: monk, Nk: ninka, I: reportImport)","sClass":"left","bSortable":false}, + {"sTitle":"Scanner Results (N: nomos, M: monk, Nk: ninka, I: reportImport, O: ojo)","sClass":"left","bSortable":false}, {"sTitle":"Actions","sClass":"left","bSortable":false,"bSearchable":false}], "sPaginationType": "listbox", "iDisplayLength": 25, diff --git a/src/www/ui/template/ui-browse-license_file-list.js.twig b/src/www/ui/template/ui-browse-license_file-list.js.twig index cfc3670a2e..4fdc561880 100644 --- a/src/www/ui/template/ui-browse-license_file-list.js.twig +++ b/src/www/ui/template/ui-browse-license_file-list.js.twig @@ -8,7 +8,7 @@ function createDirlistTable() { $('#dirlist').dataTable( { "aaData": {{ aaData }}, "aoColumns":[{"sTitle":"Files","sClass":"left"}, - {"sTitle":"Scanner Results (N: nomos, M: monk, Nk: ninka, I: reportImport)","sClass":"left"}, + {"sTitle":"Scanner Results (N: nomos, M: monk, Nk: ninka, I: reportImport, O: ojo)","sClass":"left"}, {"sTitle":"Edited Results","sClass":"left"}, {"sTitle":"Clearing Status","sClass":"clearingStatus center","bSearchable":false,"sWidth":"5%","mRender":function ( data, type, full ) { if(type!='display') return data; diff --git a/src/www/ui/ui-browse-license.php b/src/www/ui/ui-browse-license.php index fe0f37e69a..52e965b4bf 100644 --- a/src/www/ui/ui-browse-license.php +++ b/src/www/ui/ui-browse-license.php @@ -55,7 +55,7 @@ class ui_browse_license extends DefaultPlugin /** @var LicenseMap */ private $licenseProjector; /** @var array */ - protected $agentNames = array('nomos' => 'N', 'monk' => 'M', 'ninka' => 'Nk', 'reportImport' => 'I'); + protected $agentNames = array('nomos' => 'N', 'monk' => 'M', 'ninka' => 'Nk', 'reportImport' => 'I', 'ojo' => 'O'); public function __construct() { diff --git a/src/www/ui/ui-file-browse.php b/src/www/ui/ui-file-browse.php index 3579c74def..3bda9fa6c7 100644 --- a/src/www/ui/ui-file-browse.php +++ b/src/www/ui/ui-file-browse.php @@ -48,7 +48,7 @@ class ui_file_browse extends DefaultPlugin /** @var LicenseMap */ private $licenseProjector; /** @var array */ - protected $agentNames = array('nomos' => 'N', 'monk' => 'M', 'ninka' => 'Nk', 'reportImport' => 'I'); + protected $agentNames = array('nomos' => 'N', 'monk' => 'M', 'ninka' => 'Nk', 'reportImport' => 'I', 'ojo' => 'O'); public function __construct() { diff --git a/src/www/ui/ui-license-list-files.php b/src/www/ui/ui-license-list-files.php index 3cbcf2179f..6cc869536b 100644 --- a/src/www/ui/ui-license-list-files.php +++ b/src/www/ui/ui-license-list-files.php @@ -45,7 +45,7 @@ class LicenseListFiles extends FO_Plugin private $agentDao; /** @var Array */ - protected $agentNames = array('nomos' => 'N', 'monk' => 'M', 'ninka' => 'Nk', 'reportImport' => 'I'); + protected $agentNames = array('nomos' => 'N', 'monk' => 'M', 'ninka' => 'Nk', 'reportImport' => 'I', 'ojo' => 'O'); function __construct() {