@@ -18,14 +18,25 @@ package cmd
18
18
19
19
import (
20
20
"bufio"
21
+ "context"
22
+ "encoding/binary"
21
23
"errors"
22
24
"fmt"
25
+ "io"
23
26
"os"
24
27
"os/exec"
25
28
"strings"
26
29
"syscall"
27
30
28
31
"github.com/containers/toolbox/pkg/utils"
32
+ "github.com/sirupsen/logrus"
33
+ "golang.org/x/sys/unix"
34
+ )
35
+
36
+ var (
37
+ errClosed = errors .New ("closed" )
38
+
39
+ errHUP = errors .New ("HUP" )
29
40
)
30
41
31
42
// askForConfirmation prints prompt to stdout and waits for response from the
@@ -67,6 +78,158 @@ func askForConfirmation(prompt string) bool {
67
78
return retVal
68
79
}
69
80
81
+ func askForConfirmationAsyncScan (doneFD int32 ) (string , error ) {
82
+ fileFD := int32 (os .Stdin .Fd ())
83
+
84
+ pollFDs := []unix.PollFd {
85
+ {
86
+ Fd : doneFD ,
87
+ Events : unix .POLLIN ,
88
+ Revents : 0 ,
89
+ },
90
+ {
91
+ Fd : fileFD ,
92
+ Events : unix .POLLIN ,
93
+ Revents : 0 ,
94
+ },
95
+ }
96
+
97
+ for {
98
+ if _ , err := unix .Poll (pollFDs , - 1 ); err != nil {
99
+ if errors .Is (err , unix .EINTR ) {
100
+ logrus .Debugf ("Failed to poll(2): %s: ignoring" , err )
101
+ continue
102
+ }
103
+
104
+ return "" , fmt .Errorf ("poll(2) failed: %w" , err )
105
+ }
106
+
107
+ logrus .Debugf ("Returned events from poll(2): 0x%x, 0x%x\n " , pollFDs [0 ].Revents , pollFDs [1 ].Revents )
108
+
109
+ if pollFDs [0 ].Revents & unix .POLLIN != 0 {
110
+ logrus .Debug ("Returned from eventfd: POLLIN" )
111
+
112
+ for {
113
+ buffer := make ([]byte , 8 )
114
+ if n , err := unix .Read (int (doneFD ), buffer ); n != len (buffer ) || err != nil {
115
+ break
116
+ }
117
+ }
118
+
119
+ return "" , context .Canceled
120
+ }
121
+
122
+ if pollFDs [0 ].Revents & unix .POLLNVAL != 0 {
123
+ logrus .Debug ("Returned from eventfd: POLLNVAL" )
124
+ return "" , context .Canceled
125
+ }
126
+
127
+ if pollFDs [1 ].Revents & unix .POLLIN != 0 {
128
+ logrus .Debug ("Returned from /dev/stdin: POLLIN" )
129
+
130
+ scanner := bufio .NewScanner (os .Stdin )
131
+ scanner .Split (bufio .ScanLines )
132
+
133
+ if ! scanner .Scan () {
134
+ if err := scanner .Err (); err != nil {
135
+ return "" , err
136
+ } else {
137
+ return "" , io .EOF
138
+ }
139
+ }
140
+
141
+ response := scanner .Text ()
142
+ return response , nil
143
+ }
144
+
145
+ if pollFDs [1 ].Revents & unix .POLLHUP != 0 {
146
+ logrus .Debug ("Returned from /dev/stdin: POLLHUP" )
147
+ return "" , errHUP
148
+ }
149
+
150
+ if pollFDs [1 ].Revents & unix .POLLNVAL != 0 {
151
+ logrus .Debug ("Returned from /dev/stdin: POLLNVAL" )
152
+ return "" , errClosed
153
+ }
154
+ }
155
+ }
156
+
157
+ func askForConfirmationAsync (ctx context.Context , prompt string , restoreCursor bool ) (<- chan bool , <- chan error ) {
158
+ retValCh := make (chan bool )
159
+ errCh := make (chan error )
160
+
161
+ done := ctx .Done ()
162
+ doneFD := - 1
163
+ if done != nil {
164
+ eventfd , err := unix .Eventfd (0 , unix .EFD_CLOEXEC | unix .EFD_NONBLOCK )
165
+ if err != nil {
166
+ errCh <- fmt .Errorf ("eventfd(2) failed: %w" , err )
167
+ return retValCh , errCh
168
+ }
169
+
170
+ doneFD = eventfd
171
+ }
172
+
173
+ go func () {
174
+ for {
175
+ fmt .Printf ("%s " , prompt )
176
+ if restoreCursor {
177
+ fmt .Printf ("\033 [u" )
178
+ restoreCursor = false
179
+ }
180
+
181
+ response , err := askForConfirmationAsyncScan (int32 (doneFD ))
182
+ if err != nil {
183
+ errCh <- err
184
+ break
185
+ }
186
+
187
+ if response == "" {
188
+ response = "n"
189
+ } else {
190
+ response = strings .ToLower (response )
191
+ }
192
+
193
+ if response == "no" || response == "n" {
194
+ retValCh <- false
195
+ break
196
+ } else if response == "yes" || response == "y" {
197
+ retValCh <- true
198
+ break
199
+ }
200
+ }
201
+ }()
202
+
203
+ go func () {
204
+ if done == nil {
205
+ return
206
+ }
207
+
208
+ defer unix .Close (doneFD )
209
+
210
+ select {
211
+ case <- done :
212
+ // A varint-encoded uint64 takes a maximum of 10 bytes,
213
+ // as defined by binary.MaxVarintLen64. However, 1
214
+ // byte is enough to encode the number 1. See:
215
+ // https://protobuf.dev/programming-guides/encoding/
216
+ //
217
+ // An eventfd(2) file descriptor expects a uint64 to be
218
+ // given to write(2). Luckily, a varint-encoded number
219
+ // 1 happens to work.
220
+ buffer := make ([]byte , 8 )
221
+ binary .PutUvarint (buffer , 1 )
222
+
223
+ if _ , err := unix .Write (doneFD , buffer ); err != nil {
224
+ panicMsg := fmt .Sprintf ("write(2) to eventfd failed: %s" , err )
225
+ panic (panicMsg )
226
+ }
227
+ }
228
+ }()
229
+
230
+ return retValCh , errCh
231
+ }
232
+
70
233
func createErrorContainerNotFound (container string ) error {
71
234
var builder strings.Builder
72
235
fmt .Fprintf (& builder , "container %s not found\n " , container )
0 commit comments