// systemtap translator/driver
-// Copyright (C) 2005-2016 Red Hat Inc.
+// Copyright (C) 2005-2018 Red Hat Inc.
// Copyright (C) 2005 IBM Corp.
// Copyright (C) 2006 Intel Corporation.
//
#include "rpm_finder.h"
#include "task_finder.h"
#include "csclient.h"
+#include "client-nss.h"
#include "remote.h"
#include "tapsets.h"
#include "setupdwfl.h"
#ifdef HAVE_LIBREADLINE
#include "interactive.h"
#endif
+#include "bpf.h"
#if ENABLE_NLS
#include <libintl.h>
#include "stap-probe.h"
#include <cstdlib>
+#include <thread>
+#include <algorithm>
extern "C" {
#include <glob.h>
#include <time.h>
#include <unistd.h>
#include <wordexp.h>
+#include <ftw.h>
+#include <fcntl.h>
}
using namespace std;
// print probe name and variables if there
for (map<string, set<derived_probe *> >::iterator it=probe_list.begin(); it!=probe_list.end(); ++it)
{
- o << it->first; // probe name or alias
+ // probe name or alias
+ if (s.dump_mode == systemtap_session::dump_matched_probes_vars && isatty(STDOUT_FILENO))
+ o << s.colorize(it->first, "source");
+ else
+ o << it->first;
// Print the locals and arguments for -L mode only
if (s.dump_mode == systemtap_session::dump_matched_probes_vars)
if (pending_interrupts > 2) // XXX: should be configurable? time-based?
{
char msg[] = "Too many interrupts received, exiting.\n";
- int rc = write (2, msg, sizeof(msg)-1);
- if (rc) {/* Do nothing; we don't care if our last gasp went out. */ ;}
+ int fd = 2;
+
+ /* NB: writing to stderr blockingly in a signal handler is dangerous
+ * since it may prevent the stap process from quitting gracefully
+ * on receiving SIGTERM/etc signals when the stderr write buffer
+ * is full. PR23891 */
+ int flags = fcntl(fd, F_GETFL);
+ if (flags == -1)
+ _exit (1);
+
+ if (!(flags & O_NONBLOCK))
+ {
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0) {
+ int rc = write (fd, msg, sizeof(msg)-1);
+ if (rc)
+ {
+ /* Do nothing; we don't care if our last gasp went out. */
+ ;
+ }
+ }
+ } /* if ! O_NONBLOCK */
+
+ /* to avoid leaving any side-effects on the stderr device */
+ (void) fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
_exit (1);
}
}
}
-static void*
-sdt_benchmark_thread(void* p)
+static void
+sdt_benchmark_thread(unsigned long i)
{
- unsigned long i = *(unsigned long*)p;
PROBE(stap, benchmark__thread__start);
while (i--)
PROBE1(stap, benchmark, i);
PROBE(stap, benchmark__thread__end);
- return NULL;
}
gettimeofday (&tv_before, NULL);
PROBE(stap, benchmark__start);
-
- pthread_t pthreads[threads];
- for (unsigned long i = 0; i < threads; ++i)
- pthread_create(&pthreads[i], NULL, sdt_benchmark_thread, &loops);
- for (unsigned long i = 0; i < threads; ++i)
- pthread_join(pthreads[i], NULL);
-
+ {
+ vector<thread> handles;
+ for (unsigned long i = 0; i < threads; ++i)
+ handles.push_back(thread(sdt_benchmark_thread, loops));
+ for (unsigned long i = 0; i < threads; ++i)
+ handles[i].join();
+ }
PROBE(stap, benchmark__end);
times (& tms_after);
return EXIT_SUCCESS;
}
+static set<string> files;
+static string path_dir;
+
+static int collect_stp(const char* fpath, const struct stat*,
+ int typeflag, struct FTW* ftwbuf)
+{
+ if (typeflag == FTW_F)
+ {
+ const char* ext = strrchr(fpath, '.');
+ if (ext && (strcmp(".stp", ext) == 0))
+ files.insert(fpath);
+ }
+ else if (typeflag == FTW_D && ftwbuf->level > 0)
+ {
+ // Only recurse for PATH root directory
+ if (strncmp(path_dir.c_str(), fpath, path_dir.size()) != 0 ||
+ (fpath[path_dir.size()] != '/' && fpath[path_dir.size()] != '\0'))
+ return FTW_SKIP_SUBTREE;
+ }
+ return FTW_CONTINUE;
+}
+
+static int collect_stpm(const char* fpath, const struct stat*,
+ int typeflag, struct FTW* ftwbuf)
+{
+ if (typeflag == FTW_F)
+ {
+ const char* ext = strrchr(fpath, '.');
+ if (ext && (strcmp(".stpm", ext) == 0))
+ files.insert(fpath);
+ }
+ else if (typeflag == FTW_D && ftwbuf->level > 0)
+ {
+ // Only recurse for PATH root directory
+ if (strncmp(path_dir.c_str(), fpath, path_dir.size()) != 0 ||
+ (fpath[path_dir.size()] != '/' && fpath[path_dir.size()] != '\0'))
+ return FTW_SKIP_SUBTREE;
+ }
+ return FTW_CONTINUE;
+}
+
+#if !HAVE_BPF_DECLS
+int
+translate_bpf_pass (systemtap_session &)
+{
+ return 1;
+}
+#endif
// Compilation passes 0 through 4
int
// Perform passes 0 through 4 using a compile server?
if (! s.specified_servers.empty ())
{
-#if HAVE_NSS
+#if NEED_BASE_CLIENT_CODE
compile_server_client client (s);
return client.passes_0_4 ();
#else
- s.print_warning(_("Without NSS, using a compile-server is not supported by this version of systemtap"));
+ s.print_warning(_("Without NSS or HTTP client support, using a compile-server is not supported by this version of systemtap"));
// This cannot be an attempt to use a server after a local compile failed
// since --use-server-on-error is locked to 'no' if we don't have
}
// Now that no further changes to s.kernel_build_tree can occur, let's use it.
- if (s.runtime_mode == systemtap_session::kernel_runtime) {
- if ((rc = s.parse_kernel_config ()) != 0
- || (rc = s.parse_kernel_exports ()) != 0
- || (rc = s.parse_kernel_functions ()) != 0)
- {
- // Try again with a server
- s.set_try_server ();
- return rc;
- }
- }
+ if (!s.runtime_usermode_p())
+ {
+ if ((rc = s.parse_kernel_config ()) != 0
+ || (rc = s.parse_kernel_exports ()) != 0
+ || (rc = s.parse_kernel_functions ()) != 0)
+ {
+ // Try again with a server
+ s.set_try_server ();
+ return rc;
+ }
+ }
// Create the name of the C source file within the temporary
// directory. Note the _src prefix, explained in
// PASS 1a: PARSING LIBRARY SCRIPTS
PROBE1(stap, pass1a__start, &s);
+ // prep this array for tapset $n use too ... although we will reset once again for user scripts
+ s.used_args.resize(s.args.size(), false);
+
if (! s.pass_1a_complete)
{
// We need to handle the library scripts first because this pass
}
else if (s.script_file != "")
{
+ if (s.run_example)
+ {
+ files.clear();
+ path_dir = string(PKGDATADIR) + "/examples";
+ (void) nftw(path_dir.c_str(), collect_stp, 1, FTW_ACTIONRETVAL);
+
+ vector<string> examples;
+ for (auto it = files.begin(); it != files.end(); ++it)
+ {
+ string::size_type last_slash_index = it->find_last_of('/');
+ string example_name = it->substr(last_slash_index + 1);
+ if (s.script_file == example_name)
+ examples.push_back(*it);
+ }
+
+ if (examples.size() > 1)
+ {
+ cerr << "Multiple examples found: " << endl;
+ for (auto it = examples.begin(); it != examples.end(); ++it)
+ cerr << " " << *it << endl;
+ return 1;
+ }
+ else if (examples.size() == 0)
+ {
+ cerr << _F("Example '%s' was not found under '%s'", s.script_file.c_str(), path_dir.c_str()) << endl;
+ return 1;
+ }
+ else
+ s.script_file = examples[0];
+ }
+
user_file_stat_rc = stat (s.script_file.c_str(), & user_file_stat);
}
// otherwise, rc is 0 for a command line script
vector<string> version_suffixes;
- if (s.runtime_mode == systemtap_session::kernel_runtime)
+ if (!s.runtime_usermode_p())
{
// Construct kernel-versioning search path
string kvr = s.kernel_release;
runtime_prefix = "/linux";
else if (s.runtime_mode == systemtap_session::dyninst_runtime)
runtime_prefix = "/dyninst";
+ else if (s.runtime_mode == systemtap_session::bpf_runtime)
+ runtime_prefix = "/bpf";
if (!runtime_prefix.empty())
for (unsigned i=0; i<version_suffixes.size(); i+=2)
version_suffixes.insert(version_suffixes.begin() + i/2,
// now iterate upon it
for (unsigned k=0; k<version_suffixes.size(); k++)
{
- glob_t globbuf;
- string dir = s.include_path[i] + version_suffixes[k] + "/*.stpm";
- int r = glob(dir.c_str (), 0, NULL, & globbuf);
- if (r == GLOB_NOSPACE || r == GLOB_ABORTED)
- rc ++;
- // GLOB_NOMATCH is acceptable
+ int flags = FTW_ACTIONRETVAL;
+ string dir = s.include_path[i] + version_suffixes[k];
+ files.clear();
+ // we need to set this for the nftw() callback
+ path_dir = s.include_path[i] + "/PATH";
+ (void) nftw(dir.c_str(), collect_stpm, 1, flags);
unsigned prev_s_library_files = s.library_files.size();
- for (unsigned j=0; j<globbuf.gl_pathc; j++)
+ for (auto it = files.begin(); it != files.end(); ++it)
{
assert_no_interrupts();
struct stat tapset_file_stat;
- int stat_rc = stat (globbuf.gl_pathv[j], & tapset_file_stat);
+ int stat_rc = stat (it->c_str(), & tapset_file_stat);
if (stat_rc == 0 && user_file_stat_rc == 0 &&
user_file_stat.st_dev == tapset_file_stat.st_dev &&
user_file_stat.st_ino == tapset_file_stat.st_ino)
{
cerr
<< _F("usage error: macro tapset file '%s' cannot be run directly as a session script.",
- globbuf.gl_pathv[j]) << endl;
+ it->c_str()) << endl;
rc ++;
}
if (seen_library_macro_files.find(here) != seen_library_macro_files.end())
{
if (s.verbose>2)
- clog << _F("Skipping tapset \"%s\", duplicate inode.", globbuf.gl_pathv[j]) << endl;
+ clog << _F("Skipping tapset \"%s\", duplicate inode.", it->c_str()) << endl;
continue;
}
seen_library_macro_files.insert (here);
}
// PR12443: duplicate-eliminate harder
- string full_path = globbuf.gl_pathv[j];
+ string full_path = *it;
string tapset_base = s.include_path[i]; // not dir; it has arch suffixes too
if (full_path.size() > tapset_base.size())
{
if (seen_library_macro_files_names.find (tail_part) != seen_library_macro_files_names.end())
{
if (s.verbose>2)
- clog << _F("Skipping tapset \"%s\", duplicate name.", globbuf.gl_pathv[j]) << endl;
+ clog << _F("Skipping tapset \"%s\", duplicate name.", it->c_str()) << endl;
continue;
}
seen_library_macro_files_names.insert (tail_part);
}
if (s.verbose>2)
- clog << _F("Processing tapset \"%s\"", globbuf.gl_pathv[j]) << endl;
+ clog << _F("Processing tapset \"%s\"", it->c_str()) << endl;
- stapfile* f = parse_library_macros (s, globbuf.gl_pathv[j]);
+ stapfile* f = parse_library_macros (s, *it);
if (f == 0)
- s.print_warning(_F("macro tapset \"%s\" has errors, and will be skipped.", string(globbuf.gl_pathv[j]).c_str()));
+ s.print_warning(_F("macro tapset \"%s\" has errors, and will be skipped.", it->c_str()));
else
s.library_files.push_back (f);
}
unsigned next_s_library_files = s.library_files.size();
- if (s.verbose>1 && globbuf.gl_pathc > 0)
+ if (s.verbose>1 && !files.empty())
//TRANSLATORS: Searching through directories, 'processed' means 'examined so far'
clog << _F("Searched for library macro files: \"%s\", found: %zu, processed: %u",
- dir.c_str(), globbuf.gl_pathc,
+ dir.c_str(), files.size(),
(next_s_library_files-prev_s_library_files)) << endl;
-
- globfree (&globbuf);
}
}
for (unsigned i=0; i<s.include_path.size(); i++)
{
- unsigned tapset_flags = pf_guru | pf_squash_errors;
-
- // The first path is special, as it's the builtin tapset.
- // Allow all features no matter what s.compatible says.
- if (i == 0)
- tapset_flags |= pf_no_compatible;
-
// now iterate upon it
for (unsigned k=0; k<version_suffixes.size(); k++)
{
- glob_t globbuf;
- string dir = s.include_path[i] + version_suffixes[k] + "/*.stp";
- int r = glob(dir.c_str (), 0, NULL, & globbuf);
- if (r == GLOB_NOSPACE || r == GLOB_ABORTED)
- rc ++;
- // GLOB_NOMATCH is acceptable
+ int flags = FTW_ACTIONRETVAL;
+ string dir = s.include_path[i] + version_suffixes[k];
+ files.clear();
+ // we need to set this for the nftw() callback
+ path_dir = s.include_path[i] + "/PATH";
+ (void) nftw(dir.c_str(), collect_stp, 1, flags);
unsigned prev_s_library_files = s.library_files.size();
- for (unsigned j=0; j<globbuf.gl_pathc; j++)
+ for (auto it = files.begin(); it != files.end(); ++it)
{
- assert_no_interrupts();
+ unsigned tapset_flags = pf_guru | pf_squash_errors;
+
+ // The first path is special, as it's the builtin tapset.
+ // Allow all features no matter what s.compatible says.
+ if (i == 0)
+ tapset_flags |= pf_no_compatible;
+
+ if (it->find("/PATH/") != string::npos)
+ tapset_flags |= pf_auto_path;
+
+ assert_no_interrupts();
struct stat tapset_file_stat;
- int stat_rc = stat (globbuf.gl_pathv[j], & tapset_file_stat);
+ int stat_rc = stat (it->c_str(), & tapset_file_stat);
if (stat_rc == 0 && user_file_stat_rc == 0 &&
user_file_stat.st_dev == tapset_file_stat.st_dev &&
user_file_stat.st_ino == tapset_file_stat.st_ino)
{
cerr
<< _F("usage error: tapset file '%s' cannot be run directly as a session script.",
- globbuf.gl_pathv[j]) << endl;
+ it->c_str()) << endl;
rc ++;
}
if (seen_library_files.find(here) != seen_library_files.end())
{
if (s.verbose>2)
- clog << _F("Skipping tapset \"%s\", duplicate inode.", globbuf.gl_pathv[j]) << endl;
+ clog << _F("Skipping tapset \"%s\", duplicate inode.", it->c_str()) << endl;
continue;
}
seen_library_files.insert (here);
}
// PR12443: duplicate-eliminate harder
- string full_path = globbuf.gl_pathv[j];
+ string full_path = *it;
string tapset_base = s.include_path[i]; // not dir; it has arch suffixes too
if (full_path.size() > tapset_base.size())
{
if (seen_library_files_names.find (tail_part) != seen_library_files_names.end())
{
if (s.verbose>2)
- clog << _F("Skipping tapset \"%s\", duplicate name.", globbuf.gl_pathv[j]) << endl;
+ clog << _F("Skipping tapset \"%s\", duplicate name.", it->c_str()) << endl;
continue;
}
seen_library_files_names.insert (tail_part);
}
if (s.verbose>2)
- clog << _F("Processing tapset \"%s\"", globbuf.gl_pathv[j]) << endl;
+ clog << _F("Processing tapset \"%s\"", it->c_str()) << endl;
// NB: we don't need to restrict privilege only for
// /usr/share/systemtap, i.e., excluding
// a trusted environment, where client-side
// $XDG_DATA_DIRS are not passed.
- stapfile* f = parse (s, globbuf.gl_pathv[j], tapset_flags);
+ stapfile* f = parse (s, *it, tapset_flags);
if (f == 0)
- s.print_warning(_F("tapset \"%s\" has errors, and will be skipped", string(globbuf.gl_pathv[j]).c_str()));
+ s.print_warning(_F("tapset \"%s\" has errors, and will be skipped", it->c_str()));
else
- s.library_files.push_back (f);
+ {
+ assert (f->privileged);
+ s.library_files.push_back (f);
+ }
}
unsigned next_s_library_files = s.library_files.size();
- if (s.verbose>1 && globbuf.gl_pathc > 0)
+ if (s.verbose>1 && !files.empty())
//TRANSLATORS: Searching through directories, 'processed' means 'examined so far'
clog << _F("Searched: \"%s\", found: %zu, processed: %u",
- dir.c_str(), globbuf.gl_pathc,
+ dir.c_str(), files.size(),
(next_s_library_files-prev_s_library_files)) << endl;
-
- globfree (& globbuf);
}
}
+
if (s.num_errors())
rc ++;
// PASS 1b: PARSING USER SCRIPT
PROBE1(stap, pass1b__start, &s);
+ // reset for user scripts -- it's their use of $* we care about
+ // except that tapsets like argv.stp can consume $parms
+ fill(s.used_args.begin(), s.used_args.end(), false);
+
// Only try to parse a user script if the user provided one, or if we have to
// make one (as is the case for listing mode). Otherwise, s.user_script
// remains NULL.
user_flags |= pf_user_file;
if (s.script_file == "-")
{
- s.user_files.push_back (parse (s, "<input>", cin, user_flags));
+ if (s.stdin_script.str().empty())
+ s.stdin_script << cin.rdbuf();
+ s.user_files.push_back (parse (s, "<input>", s.stdin_script,
+ user_flags));
}
else if (s.script_file != "")
{
PROBE1(stap, pass2__start, &s);
rc = semantic_pass (s);
+ // http handled probes need probe information from pass 2
+ if (! s.http_servers.empty ())
+ {
+#if NEED_BASE_CLIENT_CODE
+ compile_server_client client (s);
+ return client.passes_0_4 ();
+#else
+ s.print_warning(_("Without NSS or HTTP client support, using a compile-server is not supported by this version of systemtap"));
+
+ // This cannot be an attempt to use a server after a local compile failed
+ // since --use-server-on-error is locked to 'no' if we don't have
+ // NSS.
+ assert (! s.try_server ());
+ s.print_warning(_("Ignoring --use-server"));
+#endif
+ }
+
// Dump a list of known probe point types, if requested.
if (s.dump_mode == systemtap_session::dump_probe_types)
s.pattern_root->dump (s);
// Dump the whole script if requested, or if we stop at 2
else if (s.dump_mode == systemtap_session::dump_matched_probes ||
s.dump_mode == systemtap_session::dump_matched_probes_vars ||
- (rc == 0 && s.last_pass == 2))
+ (rc == 0 && s.last_pass == 2) ||
+ (rc != 0 && s.verbose > 2))
printscript(s, cout);
times (& tms_after);
missing_rpm_list_print(s, "-debuginfo");
+ // Check for unused command line parameters. But - if the argv
+ // tapset was selected for inclusion, then the user-script need not
+ // use $* directly, so we want to suppress the warning in this case.
+ // This is hacky, but we don't have a formal way of tracking tokens
+ // that came from command line arguments so as to do set-subtraction
+ // at this point.
+ //
+ bool argc_found=false, argv_found=false;
+ for (unsigned i = 0; i<s.globals.size(); i++) {
+ if (s.globals[i]->unmangled_name == "argc") argc_found = true;
+ if (s.globals[i]->unmangled_name == "argv") argv_found = true;
+ }
+ if (!argc_found && !argv_found)
+ for (unsigned i = 0; i<s.used_args.size(); i++)
+ if (! s.used_args[i])
+ s.print_warning (_F("unused command line option $%u/@%u", i+1, i+1));
+
if (rc && !s.dump_mode && !s.try_server ())
cerr << _("Pass 2: analysis failed. [man error::pass2]") << endl;
}
// PASS 3: TRANSLATION
+ // (BPF does translation and compilation at once in pass 4.)
s.verbose = s.perpass_verbose[2];
times (& tms_before);
gettimeofday (&tv_before, NULL);
PROBE1(stap, pass3__start, &s);
- rc = translate_pass (s);
- if (! rc && s.last_pass == 3)
+ if (s.runtime_mode == systemtap_session::bpf_runtime)
{
- ifstream i (s.translated_source.c_str());
- cout << i.rdbuf();
+ times (& tms_after);
+ gettimeofday (&tv_after, NULL);
+ PROBE1(stap, pass3__end, &s);
+
+ if (s.verbose)
+ clog << _("Pass 3: pass skipped for stapbpf runtime ")
+ << TIMESPRINT
+ << endl;
+
+ assert_no_interrupts();
+ if (s.last_pass == 3)
+ return rc;
}
+ else
+ {
+ rc = translate_pass (s);
- times (& tms_after);
- gettimeofday (&tv_after, NULL);
+ if (! rc && s.last_pass == 3)
+ {
+ ifstream i (s.translated_source.c_str());
+ cout << i.rdbuf();
+ }
- if (s.verbose)
- clog << _("Pass 3: translated to C into \"")
- << s.translated_source
- << "\" "
- << getmemusage()
- << TIMESPRINT
- << endl;
+ times (& tms_after);
+ gettimeofday (&tv_after, NULL);
- if (rc && ! s.try_server ())
- cerr << _("Pass 3: translation failed. [man error::pass3]") << endl;
+ if (s.verbose)
+ clog << _("Pass 3: translated to C into \"")
+ << s.translated_source
+ << "\" "
+ << getmemusage()
+ << TIMESPRINT
+ << endl;
- PROBE1(stap, pass3__end, &s);
+ if (rc && ! s.try_server ())
+ cerr << _("Pass 3: translation failed. [man error::pass3]") << endl;
- assert_no_interrupts();
- if (rc || s.last_pass == 3) return rc;
+ PROBE1(stap, pass3__end, &s);
+
+ assert_no_interrupts();
+ if (rc || s.last_pass == 3)
+ return rc;
+ }
// PASS 4: COMPILATION
s.verbose = s.perpass_verbose[3];
gettimeofday (&tv_before, NULL);
PROBE1(stap, pass4__start, &s);
- if (s.use_cache)
+ if (s.runtime_mode == systemtap_session::bpf_runtime)
+ rc = translate_bpf_pass (s);
+ else
{
- find_stapconf_hash(s);
- get_stapconf_from_cache(s);
+ if (s.use_cache)
+ {
+ find_stapconf_hash(s);
+ get_stapconf_from_cache(s);
+ }
+ rc = compile_pass (s);
}
- rc = compile_pass (s);
- if (! rc && s.last_pass == 4)
+
+ if (! rc && s.last_pass <= 4)
{
cout << ((s.hash_path == "") ? s.module_filename() : s.hash_path);
cout << endl;
times (& tms_after);
gettimeofday (&tv_after, NULL);
- if (s.verbose) clog << _("Pass 4: compiled C into \"")
- << s.module_filename()
- << "\" "
- << TIMESPRINT
- << endl;
+ if (s.verbose)
+ {
+ if (s.runtime_mode == systemtap_session::bpf_runtime)
+ clog << _("Pass 4: compiled BPF into \"");
+ else
+ clog << _("Pass 4: compiled C into \"");
+ clog << s.module_filename() << "\" " << TIMESPRINT << endl;
+ }
if (rc && ! s.try_server ())
cerr << _("Pass 4: compilation failed. [man error::pass4]") << endl;
-
else
{
- // Update cache. Cache cleaning is kicked off at the beginning of this function.
+ // Update cache. Cache cleaning is kicked off at the
+ // beginning of this function.
if (s.use_script_cache)
add_script_to_cache(s);
if (s.use_cache && !s.runtime_usermode_p())
// We may need to save the module in $CWD if the cache was
// inaccessible for some reason.
- if (! s.use_script_cache && s.last_pass == 4)
+ if (! s.use_script_cache && s.last_pass <= 4)
s.save_module = true;
// Copy module to the current directory.
if (ss.verbose > 1)
clog << _F("Session arch: %s release: %s",
ss.architecture.c_str(), ss.kernel_release.c_str())
+ << endl
+ << _F("Build tree: \"%s\"",
+ ss.kernel_build_tree.c_str())
<< endl;
#if HAVE_NSS
// If requested, query server status. This is independent
// of other tasks.
- query_server_status (ss);
+ nss_client_query_server_status (ss);
// If requested, manage trust of servers. This is
// independent of other tasks.
- manage_server_trust (ss);
+ nss_client_manage_server_trust (ss);
#endif
// Run the passes only if a script has been specified or