Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scriptlet interpreter and prein, postin (using lua), and verifyscript support added #95

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

totph
Copy link
Contributor

@totph totph commented Oct 8, 2024

  • tar2rpm: scriptlet interpreter and prein postin support

Overriding the interpreter (defaults to /bin/sh) for all or a specific
scriptlet is possible via -interpreter or -interpreter_for NAME:INTERPRETER
where NAME is the type of scriptlet, one of:
prein postin preun postun pretrans posttrans verifyscript, e.g. prein:/bin/ksh

pretrans and posttrans, which should be lua code, are now handled. Their
interpreter '<lua>' is not overwritten, unless -interpreter_for is used.

Also added verifyscript support. When calling this during rpm -V pkgname
only the stderr output will be printed.

The scriptlets of an rpm can be checked via rpm -qp --scripts RPMFILE.


  • Support <lua> for pretrans and posttrans scriptlets

These usually do not fork interpreters, so do not override it when calling
SetScriptletInterpreter(). Note that explicity setting /bin/sh also seems
to work, and so does setting for other scriptlet. Use at your own risk!

See https://rpm-software-management.github.io/rpm/manual/lua.html


  • Support setting the scriptlet interpreter

The default interpreter '/bin/sh' can be changed via SetScriptletInterpreter(),
or on a per-scriptlet basis via SetScriptletInterpreterFor()

We use the more familiar name interpreter, RPM calls it scriptlet program.


  • Update url pointing to rpmtag.h

@totph totph force-pushed the scriptlet_programs branch from 5b5cdca to 414daf4 Compare October 8, 2024 13:56
rpm.go Outdated
}

func (r *RPM) writeSingleScriptlet(h *index, scriptlet, name string, scriptletTag, scriptletProgramTag int) error {
if scriptlet != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer using an early return to avoid this indentation. i.e.

if scriptlet == "" {
  return nil
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

rpm.go Outdated
func (r *RPM) writeSingleScriptlet(h *index, scriptlet, name string, scriptletTag, scriptletProgramTag int) error {
if scriptlet != "" {
scriptletProgram := r.scriptletProgram
// All this manual parsing instead of a simple regex to provide detailed error messages like rpmbuild:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But.. We are not rpmbuild. One of our aims is simplicity.

I really appreciate the effort you put in here, so it's not easy for me to say so, but please remove all of this verification code.

rpmbuild is a very over-engineered and complex beast, riddled with backwards-compatibility-necessitated junk. It has a ton of implicit assumptions and checks. (And a lot of other problems too).

Our implementation is simple. We don't build anything. Originally we only supported rpms that are a "bag of files". Later we consented to adding scriptles. But verifying them goes a step too far.

If the user supplies a bad script, I guess it will fail for them?

Copy link
Contributor Author

@totph totph Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, makes sense! What about going one step further and removing the -p/path/here syntax entirely? The rpmpack API could be extended to either have an additional AddPostinInterpreter (or AddPostinScriptletProgram?) for every scriptlet. Or just one SetScriptletProgramFor(what, path string) where what is postin, preun etc. Either way, in tar2rpm just a single flag for all scriptlet types in the form of
-interpreter_for "preun:/bin/foo" could be added.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!
The -p thing is messy.

I think SetScriptletProgramFor makes sense. I'm not sure what you propose by the single flag - how will a user specify separate interpreters?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@totph totph force-pushed the scriptlet_programs branch from 414daf4 to d21e2ab Compare November 1, 2024 16:56
@totph totph changed the title Support scriptlet programs and '-p/my/prog' scriptlets Scriptlet interpreter and prein, postin (using lua), and verifyscript support added Nov 1, 2024
@totph
Copy link
Contributor Author

totph commented Nov 1, 2024

Relevant --help output of tar2rpm:

-interpreter string
      interpreter (scriptlet program) to run scriptlets with (default "/bin/sh")
-interpreter_for NAME:INTERPRETER
      override interpreter for a scriptlet, format NAME:INTERPRETER, where NAME is one of
      prein postin preun postun verifyscript, e.g. prein:/bin/bash
      [+]: pretrans and posttrans use '<lua>' by default, which is not changed by -interpreter

[..]

-postin string
      postin scriptlet contents (not filename).
-posttrans string
      posttrans scriptlet contents (not filename, lua code [+])
-postun string
      postun scriptlet contents (not filename)
-prefixes string
      comma separated prefixes for relocatable packages
-prein string
      prein scriptlet contents (not filename).
-pretrans string
      pretrans scriptlet contents (not filename, lua code [+])

rpm.go Outdated
files: make(map[string]RPMFile),
customTags: make(map[int]IndexEntry),
customSigs: make(map[int]IndexEntry),
scriptletInterpreter: DefaultScriptletInterpreter,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scriptletInterpreter can be removed, it always references DefaultScriptletInterpreter now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Now used again, so SetScriptletInterpreterFor() is not overwritten by a later SetDefaultScriptletInterpreter())

Copy link
Collaborator

@jarondl jarondl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for the contribution! And for your patience.

I think I spotted a surprising behavior when setting the overall interpreter (there is a comment there), but this points out the fact that there are no tests. Can you add unit tests for the basic behavior? (Adding an interpreter, setting content, etc)?

Bonus points (imaginary of-course) if you add an actual test that uses an rpm based linux, in the "example_bazel" folder. This is a bit harder, but take a look at the examples and see if something works for you. It's harder because you have to install bazelisk and docker to make it work, so I'll understand if you skip it.

/gcbrun

rpm.go Outdated
interpreter string
}

type ScriptletType struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be exported? Or should it be scriptletType?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

rpm.go Outdated
scriptlets map[string]Scriptlet
}

type Scriptlet struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not directly used by end users, so it should be unexported.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

rpm.go Outdated
}

type Scriptlet struct {
// User provided. If `interpreter` is empty, `DefaultScriptletInterpreter` is used.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"User provided" confused me more then it helped. 90% of the values in the rpm struct can be said to be user provided one way or another. Remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

rpm.go Outdated
// AddPretrans adds a pretrans scriptlet
func (r *RPM) writeScriptlets(h *index) {
for name, s := range r.scriptlets {
if s.content != "" || s.interpreter != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer the inverse logic to reduce indentation (aka guard clause)

if s.content == "" && s.interpreter == "" {
  continue
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

rpm.go Outdated
if s.interpreter == "" {
interpreter := r.scriptletInterpreter
if tagInfo.lua {
interpreter = "<lua>"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is <lua> really how it's written in RPM? Crazy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also lua macros in .spec files which are evaluated at rpm build time!

rpm.go Outdated
if s.content != "" {
h.Add(tagInfo.tag, EntryString(s.content))
}
if s.interpreter == "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the "positive" logic would easier to follow here. Also avoid the extra indentation by using an else if.

"if the interpreter is not empty, use it. Else if it's lua, then use lua. Else use the default"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, well, moved around. But removed the indentation.

rpm.go Outdated
// all scriptlets, defaults to `/bin/sh`. Will reset anything set by `SetScriptletInterpreterFor()`.
// An emtpy string resets to the default. pretrans and posttrans usually use <lua> and are skipped.
// The scriptlets of an rpm can be checked via `rpm -qp --scripts RPMFILE`.
func (r *RPM) SetScriptletInterpreter(interpreter string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm somewhat troubled by this new logic. Imagine this: Someone adds a preinstall script, and uses SetScriptletInterpreter to use bash instead of sh.

AFAICT, this used to work as expected - only a preinstall script is added, and it uses bash. I think that with your new logic, this will set "interpreter" for all of the scripts, and will call them emptily for all of them.

Shouldn't this change the scriptletInterpreter value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, now using DefaultScriptletInterpreter for that again. Also, now an explicit SetScriptletInterpreterFor() has a higher precedence than a later SetDefaultScriptletInterpreter(), and added Default to the name.

rpm.go Outdated
if !found {
return errors.New(fmt.Sprintf("invalid scriptlet name %q", name))
}
content := ""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the magic of zero values in go to avoid someof this:

item := r.scriptlets[name]
item.interpreter = interpreter
r.scriptlets[name] = item

Should just work, correctly, no matter if the value existed before. Here, I tested it out for you:
https://go.dev/play/p/ehTutXq-MwD

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Not sure I like this magic :)

_, found := scriptletsTypes[name]
if !found {
panic(fmt.Sprintf("internal error: invalid scriptlet name %q", name))
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about zero value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@jarondl
Copy link
Collaborator

jarondl commented Nov 4, 2024

/gcbrun

totph added 5 commits February 7, 2025 16:48
The default interpreter '/bin/sh' can be changed via `SetDefaultScriptletInterpreter()`,
or on a per-scriptlet basis via `SetScriptletInterpreterFor()`

We use the more familiar name `interpreter`, RPM calls it `scriptlet program`.
These usually do not fork interpreters, so do not override them when calling
`SetDefaultScriptletInterpreter()`. Note that explicity setting /bin/sh also *seems*
to work, and so does setting <lua> for other scriptlet. Use at your own risk!

See <https://rpm-software-management.github.io/rpm/manual/lua.html>
Overriding the interpreter (defaults to /bin/sh) for all or a specific
scriptlet is possible via `-interpreter` or `-interpreter_for NAME:INTERPRETER`
where NAME is the type of scriptlet, one of:
prein postin preun postun pretrans posttrans verifyscript, e.g. `prein:/bin/ksh`

pretrans and posttrans, which should be lua code, are now handled. Their
interpreter '<lua>' is not overwritten, unless -interpreter_for is used.

Also added verifyscript support. When calling this during `rpm -V pkgname`
only the stderr output will be printed.

The scriptlets of an rpm can be checked via `rpm -qp --scripts RPMFILE`.
@totph totph force-pushed the scriptlet_programs branch from d21e2ab to a2cd292 Compare February 7, 2025 16:39
@totph
Copy link
Contributor Author

totph commented Feb 7, 2025

unit tests

Added, now writeScriptlets() does less and implicitToExplicitScriptlets() has most of the logic.

an actual test that uses an rpm based linux

I already have bazelisk and docker, the bazel-go-docker workflow isn't quite clear to me. Can you provide a basic test which builds an rpm with scriptlets and makes side effects of installing, calling rpm -V etc. visible? And to test the "interpreter only, no script content", maybe a go program visible to the test which just echos "$@" into /tmp/hello when it runs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants