Skip to content

Commit 6c3badd

Browse files
committed
main: add --oneshot=<filename> option
Signed-off-by: Masatake YAMATO <yamato@redhat.com>
1 parent 5dcf8cd commit 6c3badd

File tree

10 files changed

+203
-2
lines changed

10 files changed

+203
-2
lines changed

Tmain/oneshot.d/run.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright: 2026 Masatake YAMATO
2+
# License: GPL-2
3+
4+
CTAGS=$1
5+
6+
gen()
7+
{
8+
cat <<EOF
9+
int v;
10+
int f(void)
11+
{
12+
return 0;
13+
}
14+
EOF
15+
}
16+
17+
gen | ${CTAGS} --quiet --options=NONE --oneshot=sort-no.c --sort=no
18+
gen | ${CTAGS} --quiet --options=NONE --oneshot=sort-yes.c --sort=yes
19+
gen | ${CTAGS} --quiet --options=NONE --oneshot=x.c -x

Tmain/oneshot.d/stderr-expected.txt

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
v sort-no.c /^int v;$/;" v typeref:typename:int
2+
f sort-no.c /^int f(void)$/;" f typeref:typename:int
3+
f sort-yes.c /^int f(void)$/;" f typeref:typename:int
4+
v sort-yes.c /^int v;$/;" v typeref:typename:int
5+
f function 2 x.c int f(void)
6+
v variable 1 x.c int v;

docs/man/ctags.1.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,48 @@ Input/Output File Options
221221

222222
This option is quite esoteric and is empty by default.
223223

224+
``--oneshot=<filename>``
225+
Makes ctags behave as a filter, reading source
226+
file contents from standard input and printing their tags to
227+
standard output. When this option is enabled, the options ``-L``,
228+
``-f``, ``-o``, and ``--totals`` are ignored.
229+
230+
ctags assumes *<filename>* is the input file
231+
name of the contents. It affects language selection.
232+
*<filename>* appears as the input file name in the tags output.
233+
234+
This option is useful for extracting interesting names in command
235+
output.
236+
237+
For example, you can extract C preprocessor macro names in the source
238+
code specified by a URL:
239+
240+
.. code-block:: console
241+
242+
$ curl -s -o - \
243+
'https://raw.githubusercontent.com/torvalds/linux/refs/heads/master/fs/buffer.c' \
244+
| ./ctags --oneshot=buf.c --kinds-C='{macro}'
245+
BH_ENTRY buf.c /^#define BH_ENTRY(/;" d file:
246+
...
247+
248+
Make the output in JSON format:
249+
250+
.. code-block:: console
251+
252+
$ curl -s -o - \
253+
'https://raw.githubusercontent.com/torvalds/linux/refs/heads/master/fs/buffer.c' \
254+
| ./ctags --oneshot=buf.c --kinds-C='{macro}' --output-format=json \
255+
| jq .
256+
{
257+
"_type": "tag",
258+
"name": "BH_ENTRY",
259+
"path": "buf.c",
260+
"pattern": "/^#define BH_ENTRY(/",
261+
"file": true,
262+
"kind": "macro"
263+
}
264+
...
265+
224266
``--links[=(yes|no)]``
225267
Indicates whether symbolic links (if supported) should be followed.
226268
When disabled, symbolic links are ignored. This option is on by default.

docs/news/HEAD.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ Extend ``--map-<LANG>=`` and ``--langmap=`` options to choose a parser using reg
2727

2828
``--list-maps`` has also been extended to include regular-expression-based mappings.
2929

30+
New option: ``--oneshot=<filename>``
31+
32+
Makes @CTAGS_NAME_EXECUTABLE@ behave as a filter, reading source
33+
file contents from standard input and printing their tags to
34+
standard output.
35+
3036
Incompatible changes
3137
---------------------------------------------------------------------
3238
Messages for broken symlinks are now emitted at NOTICE level instead of

main/interactive_p.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ bool jsonErrorPrinter (const errorSelection selection, const char *const format,
3131
#endif
3232

3333
void interactiveOneshot (cookedArgs *args, void *user);
34+
void batchOneshot (cookedArgs *args, void *user);
3435

3536
enum syscallSet {
3637
syscall_coreset = 1 << 0,
38+
syscall_open = 1 << 1,
39+
syscall_ctrlset = 1 << 2,
3740
};
3841

3942
int installSyscallFilter (unsigned int set);

main/main.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -485,10 +485,13 @@ void interactiveLoop (cookedArgs *args CTAGS_ATTR_UNUSED, void *user)
485485
}
486486
#endif
487487

488-
static void oneshotCommon (const char *fname, size_t limit, bool sandbox)
488+
static void oneshotCommon (const char *fname, size_t limit, bool sandbox, int strictSyscallset)
489489
{
490490
openTagFile ();
491491

492+
if (strictSyscallset)
493+
prepareSandbox (strictSyscallset);
494+
492495
MIO *mio = mio_new_memory (NULL, 0, eRealloc, eFreeNoNullCheck);
493496
int c;
494497
size_t r = 0;
@@ -514,6 +517,18 @@ static void oneshotCommon (const char *fname, size_t limit, bool sandbox)
514517
closeTagFile (false, sandbox ? true : false);
515518
}
516519

520+
extern void batchOneshot (cookedArgs *args CTAGS_ATTR_UNUSED, void *user)
521+
{
522+
struct interactiveModeArgs *iargs = user;
523+
524+
Assert (iargs->fname);
525+
526+
if (iargs->sandbox)
527+
prepareSandbox (syscall_coreset | syscall_open | syscall_ctrlset);
528+
529+
oneshotCommon (iargs->fname, iargs->limit, iargs->sandbox, syscall_coreset);
530+
}
531+
517532
extern void interactiveOneshot (cookedArgs *args CTAGS_ATTR_UNUSED, void *user)
518533
{
519534
struct interactiveModeArgs *iargs = user;
@@ -523,7 +538,7 @@ extern void interactiveOneshot (cookedArgs *args CTAGS_ATTR_UNUSED, void *user)
523538
if (iargs->sandbox)
524539
prepareSandbox (syscall_coreset);
525540

526-
oneshotCommon (iargs->fname, iargs->limit, iargs->sandbox);
541+
oneshotCommon (iargs->fname, iargs->limit, iargs->sandbox, 0);
527542
}
528543

529544
static bool isSafeVar (const char* var)

main/options.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ static optionDescription LongOptionDescription [] = {
227227
{1,0," --filter-terminator=<string>"},
228228
{1,0," Specify <string> to print to stdout following the tags for each file"},
229229
{1,0," parsed when --filter is enabled."},
230+
{1,0," --oneshot=<filename>"},
231+
{1,0," Behave as a filter, reading file contents from standard input and"},
232+
{1,0," writing tags to standard output. <filename> is used as the input field of tags."},
230233
{1,0," --links[=(yes|no)]"},
231234
{1,0," Indicate whether symbolic links should be followed [yes]."},
232235
{1,0," --maxdepth=<N>"},
@@ -496,6 +499,8 @@ static optionDescription LongOptionDescription [] = {
496499
{1,0," Print this option summary including experimental features."},
497500
{1,0," --license"},
498501
{1,0," Print details of software license."},
502+
{1,0," --oneshot-limit=<bytes>"},
503+
{1,0," Limit the input size in the oneshot mode, in bytes (default is 32MB)."},
499504
{0,0," --print-language"},
500505
{0,0," Don't make tags file but just print the guessed language name for"},
501506
{0,0," input file."},
@@ -1678,6 +1683,27 @@ static void processHelpFullOption (
16781683
exit (0);
16791684
}
16801685

1686+
static void processOneshot (
1687+
const char *const option,
1688+
const char *const parameter)
1689+
{
1690+
if (!parameter || parameter[0] == '\0')
1691+
error (FATAL, "--%s option requires a non-empty <filename>", option);
1692+
1693+
static struct interactiveModeArgs args;
1694+
Option.interactive = INTERACTIVE_ONESHOT;
1695+
1696+
#ifdef HAVE_SECCOMP
1697+
Option.interactive |= INTERACTIVE_WITH_SANDBOX;
1698+
#endif
1699+
1700+
args.fname = parameter;
1701+
args.limit = oneshotLimit;
1702+
args.sandbox = (Option.interactive & INTERACTIVE_WITH_SANDBOX);
1703+
1704+
setMainLoop (batchOneshot, &args);
1705+
}
1706+
16811707
#ifdef HAVE_JANSSON
16821708
static void processInteractiveOption (
16831709
const char *const option,
@@ -2717,6 +2743,28 @@ static bool inOneshotMode (void)
27172743
return (Option.interactive & INTERACTIVE_ONESHOT);
27182744
}
27192745

2746+
static void oneshotSetLimit (size_t limit)
2747+
{
2748+
verbose ("adjust input limit of oneshot mode: %lu", (unsigned long)limit);
2749+
oneshotLimit = limit;
2750+
}
2751+
2752+
static void processOneshotLimit (
2753+
const char *const option, const char *const parameter)
2754+
{
2755+
if (parameter == NULL || parameter[0] == '\0')
2756+
error (FATAL, "A positive number or 0 is needed after --%s option", option);
2757+
2758+
unsigned long limit = 0;
2759+
if (!strToULong(parameter, 0, &limit))
2760+
error (FATAL, "Invalid oneshot limit: %s", parameter);
2761+
if (limit > SIZE_MAX)
2762+
error (FATAL, "Too large limit: %s (> %lu)",
2763+
parameter, (unsigned long)SIZE_MAX);
2764+
2765+
oneshotSetLimit ((size_t)limit);
2766+
}
2767+
27202768
static void processSortOption (
27212769
const char *const option, const char *const parameter)
27222770
{
@@ -3119,6 +3167,8 @@ static parametricOption ParametricOptions [] = {
31193167
{ "list-roles", processListRolesOption, true, STAGE_ANY },
31203168
{ "list-subparsers", processListSubparsersOption, true, STAGE_ANY },
31213169
{ "maxdepth", processMaxRecursionDepthOption, true, STAGE_ANY },
3170+
{ "oneshot", processOneshot, true, STAGE_ANY },
3171+
{ "oneshot-limit", processOneshotLimit, true, STAGE_ANY },
31223172
{ "optlib-dir", processOptlibDir, false, STAGE_ANY },
31233173
{ "options", processOptionFile, false, STAGE_ANY },
31243174
{ "options-maybe", processOptionFileMaybe, false, STAGE_ANY },

main/seccomp.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@
1717
#include <seccomp.h>
1818

1919

20+
static void installSyscallOpenFilter(scmp_filter_ctx ctx)
21+
{
22+
seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS (open), 0);
23+
seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS (openat), 0);
24+
verbose ("open/openat ");
25+
}
26+
27+
static void installSyscallCtrlsetFilter(scmp_filter_ctx ctx)
28+
{
29+
seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS (prctl), 0);
30+
seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS (seccomp), 0);
31+
verbose ("ctrlset ");
32+
}
33+
2034
static void installSyscallCoresetFilter(scmp_filter_ctx ctx)
2135
{
2236
// Memory allocation.
@@ -69,6 +83,10 @@ int installSyscallFilter (unsigned int set)
6983
verbose ("Entering sandbox (");
7084
if (set & syscall_coreset)
7185
installSyscallCoresetFilter (ctx);
86+
if (set & syscall_open)
87+
installSyscallOpenFilter (ctx);
88+
if (set & syscall_ctrlset)
89+
installSyscallCtrlsetFilter (ctx);
7290
verbose (")\n");
7391

7492
int err = seccomp_load (ctx);

man/ctags.1.rst.in

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,48 @@ Input/Output File Options
221221

222222
This option is quite esoteric and is empty by default.
223223

224+
``--oneshot=<filename>``
225+
Makes @CTAGS_NAME_EXECUTABLE@ behave as a filter, reading source
226+
file contents from standard input and printing their tags to
227+
standard output. When this option is enabled, the options ``-L``,
228+
``-f``, ``-o``, and ``--totals`` are ignored.
229+
230+
@CTAGS_NAME_EXECUTABLE@ assumes *<filename>* is the input file
231+
name of the contents. It affects language selection.
232+
*<filename>* appears as the input file name in the tags output.
233+
234+
This option is useful for extracting interesting names in command
235+
output.
236+
237+
For example, you can extract C preprocessor macro names in the source
238+
code specified by a URL:
239+
240+
.. code-block:: console
241+
242+
$ curl -s -o - \
243+
'https://raw.githubusercontent.com/torvalds/linux/refs/heads/master/fs/buffer.c' \
244+
| ./ctags --oneshot=buf.c --kinds-C='{macro}'
245+
BH_ENTRY buf.c /^#define BH_ENTRY(/;" d file:
246+
...
247+
248+
Make the output in JSON format:
249+
250+
.. code-block:: console
251+
252+
$ curl -s -o - \
253+
'https://raw.githubusercontent.com/torvalds/linux/refs/heads/master/fs/buffer.c' \
254+
| ./ctags --oneshot=buf.c --kinds-C='{macro}' --output-format=json \
255+
| jq .
256+
{
257+
"_type": "tag",
258+
"name": "BH_ENTRY",
259+
"path": "buf.c",
260+
"pattern": "/^#define BH_ENTRY(/",
261+
"file": true,
262+
"kind": "macro"
263+
}
264+
...
265+
224266
``--links[=(yes|no)]``
225267
Indicates whether symbolic links (if supported) should be followed.
226268
When disabled, symbolic links are ignored. This option is on by default.

0 commit comments

Comments
 (0)