This is an automated email from the ASF dual-hosted git repository.

cmcfarlen pushed a commit to branch 10.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 5c82874dc17df4add57090123495809a7ad9c647
Author: Leif Hedstrom <zw...@apache.org>
AuthorDate: Thu Apr 11 10:07:13 2024 -0600

    Allow Cripts to be used directly as @plugin (#11192)
    
    * Allow for traffic_server to compile Cripts directly
    
    This relies on an external script / application to do the
    actual work. If not configured, no new behavior is introduced.
    
    * Fixes from review
    
    (cherry picked from commit defd0f2f5b8efcf366a4651f30a4ba9d266b1b99)
---
 doc/admin-guide/files/records.yaml.en.rst  | 14 +++++
 include/proxy/http/remap/PluginDso.h       |  2 +-
 include/proxy/http/remap/PluginFactory.h   |  2 +
 include/proxy/http/remap/RemapPluginInfo.h |  2 +-
 src/proxy/http/remap/PluginDso.cc          | 24 ++++++--
 src/proxy/http/remap/PluginFactory.cc      | 24 +++++++-
 src/proxy/http/remap/RemapPluginInfo.cc    |  4 +-
 src/proxy/http/remap/UrlRewrite.cc         | 19 ++++++
 src/records/RecordsConfig.cc               |  2 +
 src/traffic_server/traffic_server.cc       |  2 +-
 tools/cripts/compiler.sh                   | 97 ++++++++++++++++++++++++++++++
 11 files changed, 182 insertions(+), 10 deletions(-)

diff --git a/doc/admin-guide/files/records.yaml.en.rst 
b/doc/admin-guide/files/records.yaml.en.rst
index a75647aba4..6b2172bb1b 100644
--- a/doc/admin-guide/files/records.yaml.en.rst
+++ b/doc/admin-guide/files/records.yaml.en.rst
@@ -4895,6 +4895,20 @@ Plug-in Configuration
    Enables (``1``) or disables (``0``) the dynamic reload feature for remap
    plugins (`remap.config`). Global plugins (`plugin.config`) do not have 
dynamic reload feature yet.
 
+.. ts:cv:: CONFIG proxy.config.plugin.compiler_path STRING ""
+
+   Specifies an optional compiler tool path for compiling plugins. This tool 
should
+   be an executable, which takes two arguments:
+
+   === ======================================================================
+   Arg Description
+   === ======================================================================
+   1   This is the path to the source file, which should be compiled
+   2   This is the path to the DSO file, which will be created and loaded
+   === ======================================================================
+
+   The script should exit with a status code of ``0`` if the compilation was 
successful.
+
 .. ts:cv:: CONFIG proxy.config.plugin.vc.default_buffer_index INT 8
    :reloadable:
    :overridable:
diff --git a/include/proxy/http/remap/PluginDso.h 
b/include/proxy/http/remap/PluginDso.h
index 41f18b943d..9c0d6e452d 100644
--- a/include/proxy/http/remap/PluginDso.h
+++ b/include/proxy/http/remap/PluginDso.h
@@ -66,7 +66,7 @@ public:
   virtual ~PluginDso();
 
   /* DSO Load, unload, get symbols from DSO */
-  virtual bool load(std::string &error);
+  virtual bool load(std::string &error, const fs::path &compilerPath);
   virtual bool unload(std::string &error);
   bool isLoaded();
   bool getSymbol(const char *symbol, void *&address, std::string &error) const;
diff --git a/include/proxy/http/remap/PluginFactory.h 
b/include/proxy/http/remap/PluginFactory.h
index 5a63a13f41..1ebcadd81e 100644
--- a/include/proxy/http/remap/PluginFactory.h
+++ b/include/proxy/http/remap/PluginFactory.h
@@ -95,6 +95,7 @@ public:
   virtual ~PluginFactory();
 
   PluginFactory &setRuntimeDir(const fs::path &runtimeDir);
+  PluginFactory &setCompilerPath(const fs::path &compilerPath);
   PluginFactory &addSearchDir(const fs::path &searchDir);
 
   RemapPluginInst *getRemapPlugin(const fs::path &configPath, int argc, char 
**argv, std::string &error, bool dynamicReloadEnabled);
@@ -112,6 +113,7 @@ protected:
 
   std::vector<fs::path> _searchDirs; /** @brief ordered list of search paths 
where we look for plugins */
   fs::path _runtimeDir;              /** @brief the path where we would create 
a temporary copies of the plugins to load */
+  fs::path _compilerPath;            /** @brief the compilation script to use 
for cripts and other non-DSO plugins */
 
   PluginInstList _instList;
 
diff --git a/include/proxy/http/remap/RemapPluginInfo.h 
b/include/proxy/http/remap/RemapPluginInfo.h
index dda7695ce6..73a91643f0 100644
--- a/include/proxy/http/remap/RemapPluginInfo.h
+++ b/include/proxy/http/remap/RemapPluginInfo.h
@@ -83,7 +83,7 @@ public:
   ~RemapPluginInfo();
 
   /* Overload to add / execute remap plugin specific tasks during the plugin 
loading */
-  bool load(std::string &error) override;
+  bool load(std::string &error, const fs::path &compilerPath) override;
 
   /* Used by the factory to invoke callbacks during plugin load, init and 
unload  */
   bool init(std::string &error) override;
diff --git a/src/proxy/http/remap/PluginDso.cc 
b/src/proxy/http/remap/PluginDso.cc
index 9da092a790..4f59167164 100644
--- a/src/proxy/http/remap/PluginDso.cc
+++ b/src/proxy/http/remap/PluginDso.cc
@@ -38,6 +38,8 @@
 #define PluginError Error
 #endif
 
+#include <cstdlib>
+
 namespace
 {
 
@@ -69,7 +71,7 @@ PluginDso::~PluginDso()
 }
 
 bool
-PluginDso::load(std::string &error)
+PluginDso::load(std::string &error, const fs::path &compilerPath)
 {
   /* Clear all errors */
   error.clear();
@@ -89,13 +91,27 @@ PluginDso::load(std::string &error)
   } else {
     PluginDbg(_dbg_ctl(), "plugin '%s' effective path: %s", 
_configPath.c_str(), _effectivePath.c_str());
 
-    /* Copy the installed plugin DSO to a runtime directory if dynamic reload 
enabled */
     std::error_code ec;
-    if (isDynamicReloadEnabled() && !copy(_effectivePath, _runtimePath, ec)) {
+
+    if (!_effectivePath.string().ends_with(".so")) {
+      if (!isDynamicReloadEnabled()) {
+        concat_error(error, "Dynamic reload must be enabled for Cript files");
+        result = false;
+      } else {
+        std::string command = compilerPath.string() + " " + 
_effectivePath.string() + " " + _runtimePath.string();
+
+        if (std::system(command.c_str()) != 0) {
+          concat_error(error, "Compile script failed");
+          result = false;
+        }
+      }
+    } else if (isDynamicReloadEnabled() && !copy(_effectivePath, _runtimePath, 
ec)) {
       concat_error(error, "failed to create a copy");
       concat_error(error, ec.message());
       result = false;
-    } else {
+    }
+
+    if (result) {
       PluginDbg(_dbg_ctl(), "plugin '%s' runtime path: %s", 
_configPath.c_str(), _runtimePath.c_str());
 
       /* Save the time for later checking if DSO got modified in consecutive 
DSO reloads */
diff --git a/src/proxy/http/remap/PluginFactory.cc 
b/src/proxy/http/remap/PluginFactory.cc
index 399da3c042..74b25a8673 100644
--- a/src/proxy/http/remap/PluginFactory.cc
+++ b/src/proxy/http/remap/PluginFactory.cc
@@ -124,6 +124,14 @@ PluginFactory::setRuntimeDir(const fs::path &runtimeDir)
   return *this;
 }
 
+PluginFactory &
+PluginFactory::setCompilerPath(const fs::path &compilerPath)
+{
+  _compilerPath = compilerPath;
+  PluginDbg(_dbg_ctl(), "set plugin compiler path %s", compilerPath.c_str());
+  return *this;
+}
+
 const char *
 PluginFactory::getUuid()
 {
@@ -175,6 +183,20 @@ PluginFactory::getRemapPlugin(const fs::path &configPath, 
int argc, char **argv,
       runtimePath /= _runtimeDir;
       runtimePath /= effectivePath.relative_path();
 
+      // Special case for Cripts
+      if (!runtimePath.string().ends_with(".so")) {
+        if (_compilerPath.empty()) {
+          error.assign("compiler path not set for compiling plugins");
+          return nullptr;
+        }
+
+        // Add .so to the source file, so e.g. example.cc.so. ToDo: libswoc 
doesn't allow appending to the extension.
+        std::string newPath = runtimePath.string();
+
+        newPath.append(".so");
+        runtimePath = newPath;
+      }
+
       fs::path parent = runtimePath.parent_path();
       PluginDbg(_dbg_ctl(), "Using effectivePath: [%s] runtimePath: [%s] 
parent: [%s]", effectivePath.c_str(), runtimePath.c_str(),
                 parent.c_str());
@@ -189,7 +211,7 @@ PluginFactory::getRemapPlugin(const fs::path &configPath, 
int argc, char **argv,
 
     plugin = new RemapPluginInfo(configPath, effectivePath, runtimePath);
     if (nullptr != plugin) {
-      if (plugin->load(error)) {
+      if (plugin->load(error, _compilerPath)) {
         if (plugin->init(error)) {
           PluginDso::loadedPlugins()->add(plugin);
           inst = RemapPluginInst::init(plugin, argc, argv, error);
diff --git a/src/proxy/http/remap/RemapPluginInfo.cc 
b/src/proxy/http/remap/RemapPluginInfo.cc
index 001fad8a86..849214ba47 100644
--- a/src/proxy/http/remap/RemapPluginInfo.cc
+++ b/src/proxy/http/remap/RemapPluginInfo.cc
@@ -77,11 +77,11 @@ RemapPluginInfo::RemapPluginInfo(const fs::path 
&configPath, const fs::path &eff
 }
 
 bool
-RemapPluginInfo::load(std::string &error)
+RemapPluginInfo::load(std::string &error, const fs::path &compilerPath)
 {
   error.clear();
 
-  if (!PluginDso::load(error)) {
+  if (!PluginDso::load(error, compilerPath)) {
     return false;
   }
 
diff --git a/src/proxy/http/remap/UrlRewrite.cc 
b/src/proxy/http/remap/UrlRewrite.cc
index 80b1460749..fa13988035 100644
--- a/src/proxy/http/remap/UrlRewrite.cc
+++ b/src/proxy/http/remap/UrlRewrite.cc
@@ -87,6 +87,25 @@ UrlRewrite::load()
   /* Initialize the plugin factory */
   
pluginFactory.setRuntimeDir(RecConfigReadRuntimeDir()).addSearchDir(RecConfigReadPluginDir());
 
+  // This is "optional", and this configuration is not set by default.
+  char buf[PATH_NAME_MAX];
+
+  buf[0] = '\0';
+  RecGetRecordString("proxy.config.plugin.compiler_path", buf, PATH_NAME_MAX);
+  if (strlen(buf) > 0) {
+    std::error_code ec;
+    fs::path compilerPath  = fs::path(buf);
+    fs::file_status status = fs::status(compilerPath, ec);
+
+    if (ec || !swoc::file::is_regular_file(status)) {
+      Error("Configured plugin compiler path '%s' is not a regular file", buf);
+      return false;
+    } else {
+      // This also adds the configuration directory (etc/trafficserver) to 
find Cripts etc.
+      
pluginFactory.setCompilerPath(compilerPath).addSearchDir(RecConfigReadConfigDir());
+    }
+  }
+
   /* Initialize the next hop strategy factory */
   std::string sf = 
RecConfigReadConfigPath("proxy.config.url_remap.strategies.filename", 
"strategies.yaml");
   Dbg(dbg_ctl_url_rewrite_regex, "strategyFactory file: %s", sf.c_str());
diff --git a/src/records/RecordsConfig.cc b/src/records/RecordsConfig.cc
index 77751488ee..00ac0c4de1 100644
--- a/src/records/RecordsConfig.cc
+++ b/src/records/RecordsConfig.cc
@@ -53,6 +53,8 @@ static const RecordElement RecordsConfig[] =
   ,
   {RECT_CONFIG, "proxy.config.bin_path", RECD_STRING, TS_BUILD_BINDIR, 
RECU_NULL, RR_REQUIRED, RECC_NULL, nullptr, RECA_READ_ONLY}
   ,
+  {RECT_CONFIG, "proxy.config.plugin.compiler_path", RECD_STRING, "", 
RECU_NULL, RR_REQUIRED, RECC_NULL, nullptr, RECA_READ_ONLY}
+  ,
   // Jira TS-21
   {RECT_CONFIG, "proxy.config.local_state_dir", RECD_STRING, 
TS_BUILD_RUNTIMEDIR, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, 
RECA_READ_ONLY}
   ,
diff --git a/src/traffic_server/traffic_server.cc 
b/src/traffic_server/traffic_server.cc
index f6df676b3e..5d7dd9c579 100644
--- a/src/traffic_server/traffic_server.cc
+++ b/src/traffic_server/traffic_server.cc
@@ -1036,7 +1036,7 @@ try_loading_plugin(plugin_type_t plugin_type, const 
fs::path &plugin_path, std::
     const auto runtime_path = temporary_directory / plugin_path.filename();
     const fs::path unused_config;
     auto plugin_info = std::make_unique<RemapPluginInfo>(unused_config, 
plugin_path, runtime_path);
-    bool loaded      = plugin_info->load(error);
+    bool loaded      = plugin_info->load(error, unused_config); // ToDo: Will 
this ever need support for cripts
     if (!fs::remove(temporary_directory, ec)) {
       fprintf(stderr, "ERROR: could not remove temporary directory '%s': 
%s\n", temporary_directory.c_str(), ec.message().c_str());
     }
diff --git a/tools/cripts/compiler.sh b/tools/cripts/compiler.sh
new file mode 100755
index 0000000000..74e6d3dd64
--- /dev/null
+++ b/tools/cripts/compiler.sh
@@ -0,0 +1,97 @@
+#!/usr/bin/env bash
+
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+##############################################################################
+# This is an example compiler script for compiling Cripts and other plugins
+# on the fly. You would set proxy.config.plugin.compiler_path to point to a
+# customized version of this script, and it will be called whenever a plugin
+# needs to be compiled via remap.config.
+##############################################################################
+
+# Configurable parts
+ATS_ROOT=/opt/ats
+CXX=clang++
+CXXFLAGS="-std=c++20 -I/opt/homebrew/include -undefined dynamic_lookup"
+
+# Probably don't need to change these ?
+STDFLAGS="-shared -fPIC -Wall -Werror -I${ATS_ROOT}/include -L${ATS_ROOT}/lib 
-lcripts"
+
+# This is optional, but if set, the script will cache the compiled shared 
objects for faster restarts/reloads
+CACHE_DIR=/tmp/ats-cache
+
+# Extract the arguments, and do some sanity checks
+SOURCE=$1
+DEST=$2
+
+SOURCE_DIR=$(dirname $SOURCE)
+DEST_DIR=$(dirname $DEST)
+SOURCE_FILE=$(basename $SOURCE)
+DEST_FILE=$(basename $DEST)
+
+cd "$SOURCE_DIR"
+if [ $(pwd) != "$SOURCE_DIR" ]; then
+    echo "Failed to cd to $SOURCE_DIR"
+    exit 1
+fi
+
+cd "$DEST_DIR"
+if [ $(pwd) != "$DEST_DIR" ]; then
+    echo "Failed to cd to $DEST_DIR"
+    exit 1
+fi
+
+if [ ! -f "$SOURCE" ]; then
+    echo "Source file $SOURCE does not exist"
+    exit 1
+fi
+
+if [ "${DEST_FILE}" != "${SOURCE_FILE}.so" ]; then
+    echo "Destination file name must match source file name with .so extension"
+    exit 1
+fi
+
+cache_file=""
+if [ -d "$CACHE_DIR" ]; then
+    cache_tree="${CACHE_DIR}${SOURCE_DIR}"
+    cache_file="${cache_tree}/${DEST_FILE}"
+
+    [ -d "$cache_tree" ] || mkdir -p "$cache_tree"
+
+    if [ "$SOURCE" -ot "$cache_file" ]; then
+        cp "$cache_file" "$DEST"
+        exit 0
+    fi
+
+    DEST="$cache_file"
+fi
+
+# Compile the plugin
+${CXX} ${CXXFLAGS} ${STDFLAGS} -o $DEST $SOURCE
+exit_code=$?
+
+if [ $exit_code -ne 0 ]; then
+    echo "Compilation failed with exit code $exit_code"
+    exit $exit_code
+fi
+
+# In case we compiled to the cache, copy from the cache to the runtime 
destination
+if [ -f "$cache_file" ]; then
+    cp "$cache_file" "${DEST_DIR}/${DEST_FILE}"
+fi
+
+exit 0

Reply via email to