diff --git a/agent/session/shell/shell_unix.go b/agent/session/shell/shell_unix.go index 50c3905252..b62faa743a 100644 --- a/agent/session/shell/shell_unix.go +++ b/agent/session/shell/shell_unix.go @@ -344,7 +344,13 @@ func (p *ShellPlugin) generateLogData(log log.T, config agentContracts.Configura ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) defer cancel() - cmdWithFlag := exec.CommandContext(ctx, startRecordSessionCmd, p.logger.logFilePath, scriptFlag, loggerCmd) + // Invoke script with options before the logfile argument. util-linux >= 2.39 rejects + // positional arguments after the logfile (ec96a89, "script: abort if unused arguments + // are given", Nov 2022). The old forms "script -c " and + // "script cmd args" both fail on util-linux >= 2.39 / >= 2.42 respectively. + // Using "-c" with the logfile last is valid on all util-linux versions and on BSD script. + // See: https://github.com/aws/amazon-ssm-agent/issues/667 + cmdWithFlag := exec.CommandContext(ctx, startRecordSessionCmd, scriptFlag, loggerCmd, p.logger.logFilePath) cmdWithFlag.Stderr = &flagStderr flagErr := cmdWithFlag.Run() if flagErr != nil { @@ -352,8 +358,9 @@ func (p *ShellPlugin) generateLogData(log log.T, config agentContracts.Configura var noFlagStderr bytes.Buffer - // some versions of "script" does not take a -c flag when passing in commands. - cmdWithoutFlag := exec.CommandContext(ctx, startRecordSessionCmd, p.logger.logFilePath, catCmd, p.logger.ipcFilePath) + // Fallback for script implementations that predate -c support. + // Arguments are ordered to satisfy util-linux >= 2.39 strict parsing. + cmdWithoutFlag := exec.CommandContext(ctx, startRecordSessionCmd, catCmd, p.logger.ipcFilePath, p.logger.logFilePath) cmdWithoutFlag.Stderr = &noFlagStderr noFlagErr := cmdWithoutFlag.Run() if noFlagErr != nil { diff --git a/agent/session/shell/shell_unix_test.go b/agent/session/shell/shell_unix_test.go index b04087ff6e..a4ffebe474 100644 --- a/agent/session/shell/shell_unix_test.go +++ b/agent/session/shell/shell_unix_test.go @@ -514,6 +514,41 @@ func (suite *ShellTestSuite) TestProcessStreamMessage() { assert.Equal(suite.T(), "testPayload", string(stdinFileContent)) } +// TestGenerateLogData verifies that generateLogData invokes script(1) with the correct +// argument order for util-linux >= 2.39. Prior to the fix, the logfile was passed as +// the first positional argument ("script -c "), which util-linux >= 2.39 +// rejects with "unexpected number of arguments". The correct form is +// "script -q -c ". +// +// This test requires the `script` binary to be present on the system (util-linux or BSD). +func (suite *ShellTestSuite) TestGenerateLogData() { + ipcFile, err := os.CreateTemp("", "ipc-*.log") + assert.Nil(suite.T(), err) + defer os.Remove(ipcFile.Name()) + + logFile, err := os.CreateTemp("", "transcript-*.log") + assert.Nil(suite.T(), err) + logFile.Close() + defer os.Remove(logFile.Name()) + + testContent := "hello from generateLogData test" + _, err = ipcFile.WriteString(testContent) + assert.Nil(suite.T(), err) + ipcFile.Close() + + suite.plugin.logger = logger{ + ipcFilePath: ipcFile.Name(), + logFilePath: logFile.Name(), + } + + err = suite.plugin.generateLogData(suite.mockLog, contracts.Configuration{}) + assert.Nil(suite.T(), err, "generateLogData should succeed; check that script(1) is installed and util-linux argument order is correct") + + transcript, err := os.ReadFile(logFile.Name()) + assert.Nil(suite.T(), err) + assert.Contains(suite.T(), string(transcript), testContent, "transcript file should contain the ipc file content") +} + // getAgentMessage constructs and returns AgentMessage with given sequenceNumber, messageType & payload func getAgentMessage(payloadType uint32, payload []byte) *mgsContracts.AgentMessage { messageUUID, _ := uuid.Parse(messageId)