summaryrefslogtreecommitdiffstats
path: root/ext/tk/lib/tkextlib/tkHTML/htmlwidget.rb
blob: d893a83cf2562c09146f844bfb484ed4c1f7a05f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
#
#  tkextlib/tkHTML/htmlwidget.rb
#                               by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)
#

require 'tk'

# call setup script for general 'tkextlib' libraries
require 'tkextlib/setup.rb'

# call setup script
require 'tkextlib/tkHTML/setup.rb'

# TkPackage.require('Tkhtml', '2.0')
TkPackage.require('Tkhtml')

module Tk
  class HTML_Widget < TkWindow
    PACKAGE_NAME = 'Tkhtml'.freeze
    def self.package_name
      PACKAGE_NAME
    end

    def self.package_version
      begin
        TkPackage.require('Tkhtml')
      rescue
        ''
      end
    end

    class ClippingWindow < TkWindow
    end
  end
end

class Tk::HTML_Widget::ClippingWindow
  WidgetClassName = 'HtmlClip'.freeze
  WidgetClassNames[WidgetClassName] = self

  HtmlClip_TBL = TkCore::INTERP.create_table

  TkCore::INTERP.init_ip_env{
    HtmlClip_TBL.mutex.synchronize{ HtmlClip_TBL.clear }
  }

  def self.new(parent, keys={})
    if parent.kind_of?(Hash)
      keys = TkComm._symbolkey2str(parent)
      parent = keys.delete('parent')
    end

    if parent.kind_of?(String)
      ppath = parent.path
    elsif parent
      ppath = parent
    else
      ppath = ''
    end
    HtmlClip_TBL.mutex.synchronize{
      return HtmlClip_TBL[ppath] if HtmlClip_TBL[ppath]
    }

    widgetname = keys.delete('widgetname')
    if widgetname =~ /^(.*)\.[^.]+$/
      ppath2 = $1
      if ppath2[0] != ?.
        ppath2 = ppath + '.' + ppath2
      end
      HtmlClip_TBL.mutex.synchronize{
        return HtmlClip_TBL[ppath2] if HtmlClip_TBL[ppath2]
      }

      ppath = ppath2
    end
    
    parent = TkComm._genobj_for_tkwidget(ppath)
    unless parent.kind_of?(Tk::HTML_Widget)
      fail ArgumentError, "parent must be a Tk::HTML_Widget instance"
    end

    super(parent)
  end

  def initialize(parent)
    @parent = parent
    @ppath = parent.path
    @path = @id = @ppath + '.x'
    HtmlClip_TBL.mutex.synchronize{
      HtmlClip_TBL[@ppath] = self
    }
  end

  def method_missing(m, *args, &b)
    @parent.__send__(m, *args, &b)
  end
end

class Tk::HTML_Widget
  include Scrollable

  TkCommandNames = ['html'.freeze].freeze
  WidgetClassName = 'Html'.freeze
  WidgetClassNames[WidgetClassName] = self

  def create_self(keys)
    if keys and keys != None
      tk_call_without_enc(self.class::TkCommandNames[0], @path, 
                          *hash_kv(keys, true))
    else
      tk_call_without_enc(self.class::TkCommandNames[0], @path)
    end
  end
  private :create_self

  def __strval_optkeys
    super() << 'base' << 'selectioncolor' << 'unvisitedcolor' << 'visitedcolor'
  end
  private :__strval_optkeys

  ###################################
  #  class methods
  ###################################
  def self.reformat(src, dst, txt)
    tk_call('html', 'reformat', src, dst, txt)
  end

  def self.url_join(*args) # args := sheme authority path query fragment
    tk_call('html', 'urljoin', *args)
  end

  def self.url_split(uri)
    tk_call('html', 'urlsplit', uri)
  end

  def self.lockcopy(src, dst)
    tk_call('html', 'lockcopy', src, dst)
  end

  def self.gzip_file(file, dat)
    tk_call('html', 'gzip', 'file', file, dat)
  end

  def self.gunzip_file(file, dat)
    tk_call('html', 'gunzip', 'file', filet)
  end

  def self.gzip_data(dat)
    tk_call('html', 'gzip', 'data', file, dat)
  end

  def self.gunzip_data(dat)
    tk_call('html', 'gunzip', 'data', filet)
  end

  def self.base64_encode(dat)
    tk_call('html', 'base64', 'encode', dat)
  end

  def self.base64_decode(dat)
    tk_call('html', 'base64', 'encode', dat)
  end

  def self.text_format(dat, len)
    tk_call('html', 'text', 'format', dat, len)
  end

  def self.xor(cmd, *args)
    tk_call('html', 'xor', cmd, *args)
  end

  def self.stdchan(cmd, channel)
    tk_call('html', 'stdchan', cmd, channel)
  end

  def self.crc32(data)
    tk_call('html', 'crc32', data)
  end

  ###################################
  #  instance methods
  ###################################
  def clipping_window
    ClippingWindow.new(self)
  end
  alias clipwin  clipping_window
  alias htmlclip clipping_window

  def bgimage(image, tid=None)
    tk_send('bgimage', image, tid)
    self
  end

  def clear()
    tk_send('clear')
    self
  end

  def coords(index=None, percent=None)
    tk_send('coords', index, percent)
  end

  def forminfo(*args)
    tk_send('forminfo', *args)
  end
  alias form_info forminfo

  def href(x, y)
    simplelist(tk_send('href', x, y))
  end

  def image_add(id, img)
    tk_send('imageadd', id, img)
    self
  end

  def image_at(x, y)
    tk_send('imageat', x, y)
  end

  def images()
    list(tk_send('images'))
  end

  def image_set(id, num)
    tk_send('imageset', id, num)
    self
  end

  def image_update(id, imgs)
    tk_send('imageupdate', id, imgs)
    self
  end

  def index(idx, count=None, unit=None)
    tk_send('index', idx, count, unit)
  end

  def insert_cursor(idx)
    tk_send('insert', idx)
  end

  def names()
    simple_list(tk_send('names'))
  end

  def on_screen(id, x, y)
    bool(tk_send('onscreen', id, x, y))
  end

  def over(x, y)
    list(tk_send('over', x, y))
  end

  def over_markup(x, y)
    list(tk_send('over', x, y, '-muponly'))
  end

  def over_attr(x, y, attrs)
    list(tk_send('overattr', x, y, attrs))
  end

  def parse(txt)
    tk_send('parse', txt)
    self
  end

  def resolver(*uri_args)
    tk_send('resolver', *uri_args)
  end

  def selection_clear()
    tk_send('selection', 'clear')
    self
  end

  def selection_set(first, last)
    tk_send('selection', 'set', first, last)
    self
  end

  def refresh(*opts)
    tk_send('refresh', *opts)
  end

  def layout()
    tk_send('layout')
  end

  def sizewindow(*args)
    tk_send('sizewindow', *args)
  end

  def postscript(*args)
    tk_send('postscript', *args)
  end

  def source()
    tk_send('source')
  end

  def plain_text(first, last)
    tk_send('text', 'ascii', first, last)
  end
  alias ascii_text plain_text
  alias text_ascii plain_text

  def text_delete(first, last)
    tk_send('text', 'delete', first, last)
    self
  end

  def html_text(first, last)
    tk_send('text', 'html', first, last)
  end
  alias text_html html_text

  def text_insert(idx, txt)
    tk_send('text', 'insert', idx, txt)
    self
  end

  def break_text(idx)
    tk_send('text', 'break', idx)
  end
  alias text_break break_text

  def text_find(txt, *args)
    tk_send('text', 'find', txt, *args)
  end

  def text_table(idx, imgs=None, attrs=None)
    tk_send('text', 'table', idx, imgs, attrs)
  end

  def token_append(tag, *args)
    tk_send('token', 'append', tag, *args)
    self
  end

  def token_delete(first, last=None)
    tk_send('token', 'delete', first, last)
    self
  end

  def token_define(*args)
    tk_send('token', 'defile', *args)
    self
  end

  def token_find(tag, *args)
    list(tk_send('token', 'find', tag, *args))
  end

  def token_get(first, last=None)
    list(tk_send('token', 'get', first, last))
  end

  def token_list(first, last=None)
    list(tk_send('token', 'list', first, last))
  end

  def token_markup(first, last=None)
    list(tk_send('token', 'markup', first, last))
  end

  def token_DOM(first, last=None)
    list(tk_send('token', 'domtokens', first, last))
  end
  alias token_dom token_DOM
  alias token_domtokens token_DOM
  alias token_dom_tokens token_DOM

  def token_get_end(idx)
    tk_send('token', 'getend', idx)
  end
  alias token_getend token_get_end

  def token_offset(start, num1, num2)
    list(tk_send('token', 'offset', start, num1, num2))
  end

  def token_get_attr(idx, name=None)
    list(tk_send('token', 'attr', idx, name))
  end

  def token_set_attr(idx, name=None, val=None)
    tk_send('token', 'attr', idx, name, val)
    self
  end

  def token_handler(tag, cmd=nil, &b)
    cmd = Proc.new(&b) if !cmd && b
    if cmd
      tk_send('token', 'handler', tag, cmd)
      return self
    else
      return tk_send('token', 'handler', tag)
    end
  end

  def token_insert(idx, tag, *args)
    tk_send('token', 'insert', idx, tag, *args)
    self
  end

  def token_attrs(*args)
    list(tk_send('token', 'attrs', *args))
  end

  def token_unique(*args)
    list(tk_send('token', 'unique', *args))
  end

  def token_on_events(*args)
    list(tk_send('token', 'onEvents', *args))
  end

  def dom_nameidx(tag, name)
    number(tk_send('dom', 'nameidx', tag, name))
  end
  alias dom_name_index dom_nameidx

  def dom_radioidx(tag, name)
    number(tk_send('dom', 'radioidx', tag, name))
  end
  alias dom_radio_index dom_radioidx

  def dom_id(*spec)
    tk_send('dom', 'id', *spec)
  end

  def dom_ids(*spec)
    list(tk_send('dom', 'ids', *spec))
  end

  def dom_value(*spec)
    list(tk_send('dom', 'value', *spec))
  end

  def dom_attr(idx)
    tk_send('dom', 'attr', idx)
  end

  def dom_formel(name)
    tk_send('dom', 'formel', name)
  end
  alias dom_form_element dom_formel

  def dom_tree(idx, val)
    list(tk_send('dom', 'tree', idx, val))
  end
end
an> g->state = %d, fd = %d\n", g, g->state, fd); n = really_read_from_socket (g, fd, buf, 4); if (n == -1) return -1; if (n == 0) { /* Hopefully this indicates the qemu child process has died. */ child_cleanup (g); return -1; } xdrmem_create (&xdr, buf, 4, XDR_DECODE); xdr_uint32_t (&xdr, &flag); xdr_destroy (&xdr); /* Read and process progress messages that happen during FileIn. */ if (flag == GUESTFS_PROGRESS_FLAG) { char buf[PROGRESS_MESSAGE_SIZE]; n = really_read_from_socket (g, fd, buf, PROGRESS_MESSAGE_SIZE); if (n == -1) return -1; if (n == 0) { child_cleanup (g); return -1; } if (g->state == BUSY && g->progress_cb) { guestfs_progress message; xdrmem_create (&xdr, buf, PROGRESS_MESSAGE_SIZE, XDR_DECODE); xdr_guestfs_progress (&xdr, &message); xdr_destroy (&xdr); g->progress_cb (g, g->progress_cb_data, message.proc, message.serial, message.position, message.total); } return 0; } if (flag != GUESTFS_CANCEL_FLAG) { error (g, _("check_for_daemon_cancellation_or_eof: read 0x%x from daemon, expected 0x%x\n"), flag, GUESTFS_CANCEL_FLAG); return -1; } return -2; } /* This writes the whole N bytes of BUF to the daemon socket. * * If the whole write is successful, it returns 0. * If there was an error, it returns -1. * If the daemon sent a cancellation message, it returns -2. * * It also checks qemu stdout for log messages and passes those up * through log_message_cb. * * It also checks for EOF (qemu died) and passes that up through the * child_cleanup function above. */ int guestfs___send_to_daemon (guestfs_h *g, const void *v_buf, size_t n) { const char *buf = v_buf; fd_set rset, rset2; fd_set wset, wset2; if (g->verbose) fprintf (stderr, "send_to_daemon: %p g->state = %d, n = %zu\n", g, g->state, n); FD_ZERO (&rset); FD_ZERO (&wset); FD_SET (g->fd[1], &rset); /* Read qemu stdout for log messages & EOF. */ FD_SET (g->sock, &rset); /* Read socket for cancellation & EOF. */ FD_SET (g->sock, &wset); /* Write to socket to send the data. */ int max_fd = MAX (g->sock, g->fd[1]); while (n > 0) { rset2 = rset; wset2 = wset; int r = select (max_fd+1, &rset2, &wset2, NULL, NULL); if (r == -1) { if (errno == EINTR || errno == EAGAIN) continue; perrorf (g, "select"); return -1; } if (FD_ISSET (g->fd[1], &rset2)) { if (read_log_message_or_eof (g, g->fd[1], 0) == -1) return -1; } if (FD_ISSET (g->sock, &rset2)) { r = check_for_daemon_cancellation_or_eof (g, g->sock); if (r < 0) return r; } if (FD_ISSET (g->sock, &wset2)) { r = write (g->sock, buf, n); if (r == -1) { if (errno == EINTR || errno == EAGAIN) continue; perrorf (g, "write"); if (errno == EPIPE) /* Disconnected from guest (RHBZ#508713). */ child_cleanup (g); return -1; } buf += r; n -= r; } } return 0; } /* This reads a single message, file chunk, launch flag or * cancellation flag from the daemon. If something was read, it * returns 0, otherwise -1. * * Both size_rtn and buf_rtn must be passed by the caller as non-NULL. * * *size_rtn returns the size of the returned message or it may be * GUESTFS_LAUNCH_FLAG or GUESTFS_CANCEL_FLAG. * * *buf_rtn is returned containing the message (if any) or will be set * to NULL. *buf_rtn must be freed by the caller. * * It also checks qemu stdout for log messages and passes those up * through log_message_cb. * * It also checks for EOF (qemu died) and passes that up through the * child_cleanup function above. * * Progress notifications are handled transparently by this function. * If the callback exists, it is called. The caller of this function * will not see GUESTFS_PROGRESS_FLAG. */ int guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn) { fd_set rset, rset2; if (g->verbose) fprintf (stderr, "recv_from_daemon: %p g->state = %d, size_rtn = %p, buf_rtn = %p\n", g, g->state, size_rtn, buf_rtn); FD_ZERO (&rset); FD_SET (g->fd[1], &rset); /* Read qemu stdout for log messages & EOF. */ FD_SET (g->sock, &rset); /* Read socket for data & EOF. */ int max_fd = MAX (g->sock, g->fd[1]); *size_rtn = 0; *buf_rtn = NULL; char lenbuf[4]; /* nr is the size of the message, but we prime it as -4 because we * have to read the message length word first. */ ssize_t nr = -4; for (;;) { ssize_t message_size = *size_rtn != GUESTFS_PROGRESS_FLAG ? *size_rtn : PROGRESS_MESSAGE_SIZE; if (nr >= message_size) break; rset2 = rset; int r = select (max_fd+1, &rset2, NULL, NULL, NULL); if (r == -1) { if (errno == EINTR || errno == EAGAIN) continue; perrorf (g, "select"); free (*buf_rtn); *buf_rtn = NULL; return -1; } if (FD_ISSET (g->fd[1], &rset2)) { if (read_log_message_or_eof (g, g->fd[1], 0) == -1) { free (*buf_rtn); *buf_rtn = NULL; return -1; } } if (FD_ISSET (g->sock, &rset2)) { if (nr < 0) { /* Have we read the message length word yet? */ r = read (g->sock, lenbuf+nr+4, -nr); if (r == -1) { if (errno == EINTR || errno == EAGAIN) continue; int err = errno; perrorf (g, "read"); /* Under some circumstances we see "Connection reset by peer" * here when the child dies suddenly. Catch this and call * the cleanup function, same as for EOF. */ if (err == ECONNRESET) child_cleanup (g); return -1; } if (r == 0) { error (g, _("unexpected end of file when reading from daemon")); child_cleanup (g); return -1; } nr += r; if (nr < 0) /* Still not got the whole length word. */ continue; XDR xdr; xdrmem_create (&xdr, lenbuf, 4, XDR_DECODE); xdr_uint32_t (&xdr, size_rtn); xdr_destroy (&xdr); /* *size_rtn changed, recalculate message_size */ message_size = *size_rtn != GUESTFS_PROGRESS_FLAG ? *size_rtn : PROGRESS_MESSAGE_SIZE; if (*size_rtn == GUESTFS_LAUNCH_FLAG) { if (g->state != LAUNCHING) error (g, _("received magic signature from guestfsd, but in state %d"), g->state); else { g->state = READY; if (g->launch_done_cb) g->launch_done_cb (g, g->launch_done_cb_data); } return 0; } else if (*size_rtn == GUESTFS_CANCEL_FLAG) return 0; else if (*size_rtn == GUESTFS_PROGRESS_FLAG) /*FALLTHROUGH*/; /* If this happens, it's pretty bad and we've probably lost * synchronization. */ else if (*size_rtn > GUESTFS_MESSAGE_MAX) { error (g, _("message length (%u) > maximum possible size (%d)"), (unsigned) *size_rtn, GUESTFS_MESSAGE_MAX); return -1; } /* Allocate the complete buffer, size now known. */ *buf_rtn = safe_malloc (g, message_size); /*FALLTHROUGH*/ } size_t sizetoread = message_size - nr; if (sizetoread > BUFSIZ) sizetoread = BUFSIZ; r = read (g->sock, (char *) (*buf_rtn) + nr, sizetoread); if (r == -1) { if (errno == EINTR || errno == EAGAIN) continue; perrorf (g, "read"); free (*buf_rtn); *buf_rtn = NULL; return -1; } if (r == 0) { error (g, _("unexpected end of file when reading from daemon")); child_cleanup (g); free (*buf_rtn); *buf_rtn = NULL; return -1; } nr += r; } } /* Got the full message, caller can start processing it. */ #ifdef ENABLE_PACKET_DUMP if (g->verbose) { ssize_t i, j; for (i = 0; i < nr; i += 16) { printf ("%04zx: ", i); for (j = i; j < MIN (i+16, nr); ++j) printf ("%02x ", (*(unsigned char **)buf_rtn)[j]); for (; j < i+16; ++j) printf (" "); printf ("|"); for (j = i; j < MIN (i+16, nr); ++j) if (c_isprint ((*(char **)buf_rtn)[j])) printf ("%c", (*(char **)buf_rtn)[j]); else printf ("."); for (; j < i+16; ++j) printf (" "); printf ("|\n"); } } #endif if (*size_rtn == GUESTFS_PROGRESS_FLAG) { if (g->state == BUSY && g->progress_cb) { guestfs_progress message; XDR xdr; xdrmem_create (&xdr, *buf_rtn, PROGRESS_MESSAGE_SIZE, XDR_DECODE); xdr_guestfs_progress (&xdr, &message); xdr_destroy (&xdr); g->progress_cb (g, g->progress_cb_data, message.proc, message.serial, message.position, message.total); } free (*buf_rtn); *buf_rtn = NULL; /* Process next message. */ return guestfs___recv_from_daemon (g, size_rtn, buf_rtn); } return 0; } /* This is very much like recv_from_daemon above, but g->sock is * a listening socket and we are accepting a new connection on * that socket instead of reading anything. Returns the newly * accepted socket. */ int guestfs___accept_from_daemon (guestfs_h *g) { fd_set rset, rset2; if (g->verbose) fprintf (stderr, "accept_from_daemon: %p g->state = %d\n", g, g->state); FD_ZERO (&rset); FD_SET (g->fd[1], &rset); /* Read qemu stdout for log messages & EOF. */ FD_SET (g->sock, &rset); /* Read socket for accept. */ int max_fd = MAX (g->sock, g->fd[1]); int sock = -1; while (sock == -1) { /* If the qemu process has died, clean up the zombie (RHBZ#579155). * By partially polling in the select below we ensure that this * function will be called eventually. */ waitpid (g->pid, NULL, WNOHANG); rset2 = rset; struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; int r = select (max_fd+1, &rset2, NULL, NULL, &tv); if (r == -1) { if (errno == EINTR || errno == EAGAIN) continue; perrorf (g, "select"); return -1; } if (FD_ISSET (g->fd[1], &rset2)) { if (read_log_message_or_eof (g, g->fd[1], 1) == -1) return -1; } if (FD_ISSET (g->sock, &rset2)) { sock = accept (g->sock, NULL, NULL); if (sock == -1) { if (errno == EINTR || errno == EAGAIN) continue; perrorf (g, "accept"); return -1; } } } return sock; } int guestfs___send (guestfs_h *g, int proc_nr, uint64_t progress_hint, uint64_t optargs_bitmask, xdrproc_t xdrp, char *args) { struct guestfs_message_header hdr; XDR xdr; u_int32_t len; int serial = g->msg_next_serial++; int r; char *msg_out; size_t msg_out_size; if (g->state != BUSY) { error (g, _("guestfs___send: state %d != BUSY"), g->state); return -1; } /* We have to allocate this message buffer on the heap because * it is quite large (although will be mostly unused). We * can't allocate it on the stack because in some environments * we have quite limited stack space available, notably when * running in the JVM. */ msg_out = safe_malloc (g, GUESTFS_MESSAGE_MAX + 4); xdrmem_create (&xdr, msg_out + 4, GUESTFS_MESSAGE_MAX, XDR_ENCODE); /* Serialize the header. */ hdr.prog = GUESTFS_PROGRAM; hdr.vers = GUESTFS_PROTOCOL_VERSION; hdr.proc = proc_nr; hdr.direction = GUESTFS_DIRECTION_CALL; hdr.serial = serial; hdr.status = GUESTFS_STATUS_OK; hdr.progress_hint = progress_hint; hdr.optargs_bitmask = optargs_bitmask; if (!xdr_guestfs_message_header (&xdr, &hdr)) { error (g, _("xdr_guestfs_message_header failed")); goto cleanup1; } /* Serialize the args. If any, because some message types * have no parameters. */ if (xdrp) { if (!(*xdrp) (&xdr, args)) { error (g, _("dispatch failed to marshal args")); goto cleanup1; } } /* Get the actual length of the message, resize the buffer to match * the actual length, and write the length word at the beginning. */ len = xdr_getpos (&xdr); xdr_destroy (&xdr); msg_out = safe_realloc (g, msg_out, len + 4); msg_out_size = len + 4; xdrmem_create (&xdr, msg_out, 4, XDR_ENCODE); xdr_uint32_t (&xdr, &len); again: r = guestfs___send_to_daemon (g, msg_out, msg_out_size); if (r == -2) /* Ignore stray daemon cancellations. */ goto again; if (r == -1) goto cleanup1; free (msg_out); return serial; cleanup1: free (msg_out); return -1; } static int cancel = 0; /* XXX Implement file cancellation. */ static int send_file_chunk (guestfs_h *g, int cancel, const char *buf, size_t len); static int send_file_data (guestfs_h *g, const char *buf, size_t len); static int send_file_cancellation (guestfs_h *g); static int send_file_complete (guestfs_h *g); /* Send a file. * Returns: * 0 OK * -1 error * -2 daemon cancelled (we must read the error message) */ int guestfs___send_file (guestfs_h *g, const char *filename) { char buf[GUESTFS_MAX_CHUNK_SIZE]; int fd, r, err; fd = open (filename, O_RDONLY); if (fd == -1) { perrorf (g, "open: %s", filename); send_file_cancellation (g); /* Daemon sees cancellation and won't reply, so caller can * just return here. */ return -1; } /* Send file in chunked encoding. */ while (!cancel) { r = read (fd, buf, sizeof buf); if (r == -1 && (errno == EINTR || errno == EAGAIN)) continue; if (r <= 0) break; err = send_file_data (g, buf, r); if (err < 0) { if (err == -2) /* daemon sent cancellation */ send_file_cancellation (g); return err; } } if (cancel) { /* cancel from either end */ send_file_cancellation (g); return -1; } if (r == -1) { perrorf (g, "read: %s", filename); send_file_cancellation (g); return -1; } /* End of file, but before we send that, we need to close * the file and check for errors. */ if (close (fd) == -1) { perrorf (g, "close: %s", filename); send_file_cancellation (g); return -1; } return send_file_complete (g); } /* Send a chunk of file data. */ static int send_file_data (guestfs_h *g, const char *buf, size_t len) { return send_file_chunk (g, 0, buf, len); } /* Send a cancellation message. */ static int send_file_cancellation (guestfs_h *g) { return send_file_chunk (g, 1, NULL, 0); } /* Send a file complete chunk. */ static int send_file_complete (guestfs_h *g) { char buf[1]; return send_file_chunk (g, 0, buf, 0); } static int send_file_chunk (guestfs_h *g, int cancel, const char *buf, size_t buflen) { u_int32_t len; int r; guestfs_chunk chunk; XDR xdr; char *msg_out; size_t msg_out_size; if (g->state != BUSY) { error (g, _("send_file_chunk: state %d != READY"), g->state); return -1; } /* Allocate the chunk buffer. Don't use the stack to avoid * excessive stack usage and unnecessary copies. */ msg_out = safe_malloc (g, GUESTFS_MAX_CHUNK_SIZE + 4 + 48); xdrmem_create (&xdr, msg_out + 4, GUESTFS_MAX_CHUNK_SIZE + 48, XDR_ENCODE); /* Serialize the chunk. */ chunk.cancel = cancel; chunk.data.data_len = buflen; chunk.data.data_val = (char *) buf; if (!xdr_guestfs_chunk (&xdr, &chunk)) { error (g, _("xdr_guestfs_chunk failed (buf = %p, buflen = %zu)"), buf, buflen); xdr_destroy (&xdr); goto cleanup1; } len = xdr_getpos (&xdr); xdr_destroy (&xdr); /* Reduce the size of the outgoing message buffer to the real length. */ msg_out = safe_realloc (g, msg_out, len + 4); msg_out_size = len + 4; xdrmem_create (&xdr, msg_out, 4, XDR_ENCODE); xdr_uint32_t (&xdr, &len); r = guestfs___send_to_daemon (g, msg_out, msg_out_size); /* Did the daemon send a cancellation message? */ if (r == -2) { if (g->verbose) fprintf (stderr, "got daemon cancellation\n"); return -2; } if (r == -1) goto cleanup1; free (msg_out); return 0; cleanup1: free (msg_out); return -1; } /* Receive a reply. */ int guestfs___recv (guestfs_h *g, const char *fn, guestfs_message_header *hdr, guestfs_message_error *err, xdrproc_t xdrp, char *ret) { XDR xdr; void *buf; uint32_t size; int r; again: r = guestfs___recv_from_daemon (g, &size, &buf); if (r == -1) return -1; /* This can happen if a cancellation happens right at the end * of us sending a FileIn parameter to the daemon. Discard. The * daemon should send us an error message next. */ if (size == GUESTFS_CANCEL_FLAG) goto again; if (size == GUESTFS_LAUNCH_FLAG) { error (g, "%s: received unexpected launch flag from daemon when expecting reply", fn); return -1; } xdrmem_create (&xdr, buf, size, XDR_DECODE); if (!xdr_guestfs_message_header (&xdr, hdr)) { error (g, "%s: failed to parse reply header", fn); xdr_destroy (&xdr); free (buf); return -1; } if (hdr->status == GUESTFS_STATUS_ERROR) { if (!xdr_guestfs_message_error (&xdr, err)) { error (g, "%s: failed to parse reply error", fn); xdr_destroy (&xdr); free (buf); return -1; } } else { if (xdrp && ret && !xdrp (&xdr, ret)) { error (g, "%s: failed to parse reply", fn); xdr_destroy (&xdr); free (buf); return -1; } } xdr_destroy (&xdr); free (buf); return 0; } /* Receive a file. */ /* Returns -1 = error, 0 = EOF, > 0 = more data */ static ssize_t receive_file_data (guestfs_h *g, void **buf); int guestfs___recv_file (guestfs_h *g, const char *filename) { void *buf; int fd, r; fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666); if (fd == -1) { perrorf (g, "open: %s", filename); goto cancel; } /* Receive the file in chunked encoding. */ while ((r = receive_file_data (g, &buf)) > 0) { if (xwrite (fd, buf, r) == -1) { perrorf (g, "%s: write", filename); free (buf); goto cancel; } free (buf); } if (r == -1) { error (g, _("%s: error in chunked encoding"), filename); return -1; } if (close (fd) == -1) { perrorf (g, "close: %s", filename); return -1; } return 0; cancel: ; /* Send cancellation message to daemon, then wait until it * cancels (just throwing away data). */ XDR xdr; char fbuf[4]; uint32_t flag = GUESTFS_CANCEL_FLAG; if (g->verbose) fprintf (stderr, "%s: waiting for daemon to acknowledge cancellation\n", __func__); xdrmem_create (&xdr, fbuf, sizeof fbuf, XDR_ENCODE); xdr_uint32_t (&xdr, &flag); xdr_destroy (&xdr); if (xwrite (g->sock, fbuf, sizeof fbuf) == -1) { perrorf (g, _("write to daemon socket")); return -1; } while (receive_file_data (g, NULL) > 0) ; /* just discard it */ return -1; } /* Receive a chunk of file data. */ /* Returns -1 = error, 0 = EOF, > 0 = more data */ static ssize_t receive_file_data (guestfs_h *g, void **buf_r) { int r; void *buf; uint32_t len; XDR xdr; guestfs_chunk chunk; r = guestfs___recv_from_daemon (g, &len, &buf); if (r == -1) { error (g, _("receive_file_data: parse error in reply callback")); return -1; } if (len == GUESTFS_LAUNCH_FLAG || len == GUESTFS_CANCEL_FLAG) { error (g, _("receive_file_data: unexpected flag received when reading file chunks")); return -1; } memset (&chunk, 0, sizeof chunk); xdrmem_create (&xdr, buf, len, XDR_DECODE); if (!xdr_guestfs_chunk (&xdr, &chunk)) { error (g, _("failed to parse file chunk")); free (buf); return -1; } xdr_destroy (&xdr); /* After decoding, the original buffer is no longer used. */ free (buf); if (chunk.cancel) { error (g, _("file receive cancelled by daemon")); free (chunk.data.data_val); return -1; } if (chunk.data.data_len == 0) { /* end of transfer */ free (chunk.data.data_val); return 0; } if (buf_r) *buf_r = chunk.data.data_val; else free (chunk.data.data_val); /* else caller frees */ return chunk.data.data_len; }