1/* Copyright (C) 2002 The gtkmm Development Team
2 *
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2.1 of the License, or (at your option) any later version.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include <glibmm/exceptionhandler.h>
18#include <glibmm/iochannel.h>
19#include <glibmm/utility.h>
20#include <glibmm/main.h>
21
22namespace
23{
24
25// Glib::IOChannel reference counting issues:
26//
27// Normally, you'd expect that the C++ object stays around as long as the
28// C instance does.  Also Glib::wrap() usually returns always the same C++
29// wrapper object for a single C instance.
30//
31// Unfortunately it isn't possible to implement these features if we didn't
32// create the underlying GIOChannel.  That is, when wrapping existing
33// GIOChannel instances such as returned by e.g. g_io_channel_unix_new() or
34// g_io_channel_new_file().  Neither is there a way to hook up a wrapper
35// object in an existing GIOChannel, nor exists any destroy notification.
36//
37// So that means:  If the IOChannel is implemented in C++ -- that is, our
38// GlibmmIOChannel backend is used -- we use the GIOChannel reference
39// counting mechanism.  If the IOChannel backend is unknown, then the
40// wrapper instance holds always exactly one reference to the GIOChannel.
41// The wrapper object itself is then managed via our own refcounting
42// mechanism.  To do that a utility class ForeignIOChannel is introduced to
43// override reference() and unreference().
44
45class ForeignIOChannel : public Glib::IOChannel
46{
47public:
48  ForeignIOChannel(GIOChannel* gobject, bool take_copy)
49  : Glib::IOChannel(gobject, take_copy), ref_count_(0)
50  {
51  }
52
53  void reference() const override;
54  void unreference() const override;
55
56private:
57  mutable int ref_count_;
58};
59
60void
61ForeignIOChannel::reference() const
62{
63  ++ref_count_;
64}
65
66void
67ForeignIOChannel::unreference() const
68{
69  if (!(--ref_count_))
70    delete this;
71}
72
73} // anonymous namespace
74
75namespace Glib
76{
77
78class GlibmmIOChannel
79{
80public:
81  GIOChannel base;
82  Glib::IOChannel* wrapper;
83
84  static const GIOFuncs vfunc_table;
85
86  static GIOStatus io_read(
87    GIOChannel* channel, char* buf, gsize count, gsize* bytes_read, GError** err);
88
89  static GIOStatus io_write(
90    GIOChannel* channel, const char* buf, gsize count, gsize* bytes_written, GError** err);
91
92  static GIOStatus io_seek(GIOChannel* channel, gint64 offset, GSeekType type, GError** err);
93  static GIOStatus io_close(GIOChannel* channel, GError** err);
94
95  static GSource* io_create_watch(GIOChannel* channel, GIOCondition condition);
96  static void io_free(GIOChannel* channel);
97
98  static GIOStatus io_set_flags(GIOChannel* channel, GIOFlags flags, GError** err);
99  static GIOFlags io_get_flags(GIOChannel* channel);
100};
101
102// static
103const GIOFuncs GlibmmIOChannel::vfunc_table = {
104  &GlibmmIOChannel::io_read, &GlibmmIOChannel::io_write, &GlibmmIOChannel::io_seek,
105  &GlibmmIOChannel::io_close, &GlibmmIOChannel::io_create_watch, &GlibmmIOChannel::io_free,
106  &GlibmmIOChannel::io_set_flags, &GlibmmIOChannel::io_get_flags,
107};
108
109/**** GLib::IOChannel ******************************************************/
110
111/* Construct a custom C++-implemented IOChannel.  GlibmmIOChannel is an
112 * extended GIOChannel struct which allows us to hook up a pointer to this
113 * persistent wrapper instance.
114 */
115IOChannel::IOChannel() : gobject_(static_cast<GIOChannel*>(g_malloc(sizeof(GlibmmIOChannel))))
116{
117  g_io_channel_init(gobject_);
118  gobject_->funcs = const_cast<GIOFuncs*>(&GlibmmIOChannel::vfunc_table);
119
120  reinterpret_cast<GlibmmIOChannel*>(gobject_)->wrapper = this;
121}
122
123IOChannel::IOChannel(IOChannel&& other) noexcept : sigc::trackable(std::move(other)),
124                                                   gobject_(std::move(other.gobject_))
125{
126  other.gobject_ = nullptr;
127}
128
129IOChannel&
130IOChannel::operator=(IOChannel&& other) noexcept
131{
132  sigc::trackable::operator=(std::move(other));
133
134  release_gobject();
135
136  gobject_ = std::move(other.gobject_);
137  other.gobject_ = nullptr;
138
139  return *this;
140}
141
142/* Construct an IOChannel wrapper for an already created GIOChannel.
143 * See the comment at the top of this file for an explanation of the
144 * problems with this approach.
145 */
146IOChannel::IOChannel(GIOChannel* gobject, bool take_copy) : gobject_(gobject)
147{
148  // This ctor should never be called for GlibmmIOChannel instances.
149  g_assert(gobject != nullptr);
150  g_assert(gobject->funcs != &GlibmmIOChannel::vfunc_table);
151
152  if (take_copy)
153    g_io_channel_ref(gobject_);
154}
155
156void
157IOChannel::release_gobject()
158{
159  if (gobject_)
160  {
161    // Check whether this IOChannel is implemented in C++, i.e. whether it
162    // uses our GlibmmIOChannel forwarding backend.  Normally, this will never
163    // be true because the wrapper should only be deleted in the io_free()
164    // callback, which clears gobject_ before deleting.  But in case the ctor
165    // of a derived class threw an exception the GIOChannel must be destroyed
166    // prematurely.
167    //
168    if (gobject_->funcs == &GlibmmIOChannel::vfunc_table)
169    {
170      // Disconnect the wrapper object so that it won't be deleted twice.
171      reinterpret_cast<GlibmmIOChannel*>(gobject_)->wrapper = nullptr;
172    }
173
174    const auto tmp_gobject = gobject_;
175    gobject_ = nullptr;
176
177    g_io_channel_unref(tmp_gobject);
178  }
179}
180
181IOChannel::~IOChannel()
182{
183  release_gobject();
184}
185
186Glib::RefPtr<IOChannel>
187IOChannel::create_from_file(const std::string& filename, const std::string& mode)
188{
189  GError* gerror = nullptr;
190  const auto channel = g_io_channel_new_file(filename.c_str(), mode.c_str(), &gerror);
191
192  if (gerror)
193  {
194    Glib::Error::throw_exception(gerror);
195  }
196
197  return Glib::wrap(channel, false);
198}
199
200Glib::RefPtr<IOChannel>
201IOChannel::create_from_fd(int fd)
202{
203  return Glib::wrap(g_io_channel_unix_new(fd), false);
204}
205
206#ifdef G_OS_WIN32
207
208Glib::RefPtr<IOChannel>
209IOChannel::create_from_win32_fd(int fd)
210{
211  return Glib::wrap(g_io_channel_win32_new_fd(fd), false);
212}
213
214Glib::RefPtr<IOChannel>
215IOChannel::create_from_win32_socket(int socket)
216{
217  return Glib::wrap(g_io_channel_win32_new_socket(socket), false);
218}
219
220#endif /* G_OS_WIN32 */
221
222IOStatus
223IOChannel::write(const Glib::ustring& str)
224{
225  gsize bytes_written = 0;
226  return write(str.data(), str.bytes(), bytes_written);
227}
228
229IOStatus
230IOChannel::read_line(Glib::ustring& line)
231{
232  GError* gerror = nullptr;
233  gsize bytes = 0;
234  char* pch_buf = nullptr;
235
236  const auto status = g_io_channel_read_line(gobj(), &pch_buf, &bytes, nullptr, &gerror);
237  auto buf = make_unique_ptr_gfree(pch_buf);
238  if (gerror)
239  {
240    Glib::Error::throw_exception(gerror);
241  }
242
243  if (buf.get())
244    line.assign(buf.get(), buf.get() + bytes);
245  else
246    line.erase();
247
248  return (IOStatus)status;
249}
250
251IOStatus
252IOChannel::read_to_end(Glib::ustring& str)
253{
254  GError* gerror = nullptr;
255  gsize bytes = 0;
256  char* pch_buf = nullptr;
257
258  const auto status = g_io_channel_read_to_end(gobj(), &pch_buf, &bytes, &gerror);
259  auto buf = make_unique_ptr_gfree(pch_buf);
260  if (gerror)
261  {
262    Glib::Error::throw_exception(gerror);
263  }
264
265  if (buf.get())
266    str.assign(buf.get(), buf.get() + bytes);
267  else
268    str.erase();
269
270  return (IOStatus)status;
271}
272
273IOStatus
274IOChannel::read(Glib::ustring& str, gsize count)
275{
276  auto buf = make_unique_ptr_gfree(g_new(char, count));
277  GError* gerror = nullptr;
278  gsize bytes = 0;
279
280  const auto status = g_io_channel_read_chars(gobj(), buf.get(), count, &bytes, &gerror);
281
282  if (gerror)
283  {
284    Glib::Error::throw_exception(gerror);
285  }
286
287  if (buf.get())
288    str.assign(buf.get(), buf.get() + bytes);
289  else
290    str.erase();
291
292  return (IOStatus)status;
293}
294
295IOStatus
296IOChannel::set_encoding(const std::string& encoding)
297{
298  GError* gerror = nullptr;
299
300  const auto status = g_io_channel_set_encoding(gobj(), Glib::c_str_or_nullptr(encoding), &gerror);
301
302  if (gerror)
303  {
304    Glib::Error::throw_exception(gerror);
305  }
306
307  return (IOStatus)status;
308}
309
310std::string
311IOChannel::get_encoding() const
312{
313  const char* const encoding = g_io_channel_get_encoding(gobject_);
314  return convert_const_gchar_ptr_to_stdstring(encoding);
315}
316
317void
318IOChannel::set_line_term(const std::string& term)
319{
320  if (term.empty())
321    g_io_channel_set_line_term(gobj(), nullptr, 0);
322  else
323    g_io_channel_set_line_term(gobj(), term.data(), term.size());
324}
325
326std::string
327IOChannel::get_line_term() const
328{
329  int len = 0;
330  const char* const term = g_io_channel_get_line_term(gobject_, &len);
331
332  return (term) ? std::string(term, len) : std::string();
333}
334
335Glib::RefPtr<IOSource>
336IOChannel::create_watch(IOCondition condition)
337{
338  // The corresponding unreference() takes place in the dtor
339  // of the Glib::RefPtr<IOChannel> object below.
340  reference();
341  return IOSource::create(Glib::RefPtr<IOChannel>(this), condition);
342}
343
344IOStatus
345IOChannel::read_vfunc(char*, gsize, gsize&)
346{
347  g_assert_not_reached();
348  return IO_STATUS_ERROR;
349}
350
351IOStatus
352IOChannel::write_vfunc(const char*, gsize, gsize&)
353{
354  g_assert_not_reached();
355  return IO_STATUS_ERROR;
356}
357
358IOStatus IOChannel::seek_vfunc(gint64, SeekType)
359{
360  g_assert_not_reached();
361  return IO_STATUS_ERROR;
362}
363
364IOStatus
365IOChannel::close_vfunc()
366{
367  g_assert_not_reached();
368  return IO_STATUS_ERROR;
369}
370
371Glib::RefPtr<Glib::Source> IOChannel::create_watch_vfunc(IOCondition)
372{
373  g_assert_not_reached();
374  return Glib::RefPtr<Glib::Source>();
375}
376
377IOStatus IOChannel::set_flags_vfunc(IOFlags)
378{
379  g_assert_not_reached();
380  return IO_STATUS_ERROR;
381}
382
383IOFlags
384IOChannel::get_flags_vfunc()
385{
386  g_assert_not_reached();
387  return IOFlags(0);
388}
389
390void
391IOChannel::reference() const
392{
393  g_io_channel_ref(gobject_);
394}
395
396void
397IOChannel::unreference() const
398{
399  g_io_channel_unref(gobject_);
400}
401
402Glib::RefPtr<IOChannel>
403wrap(GIOChannel* gobject, bool take_copy)
404{
405  IOChannel* cpp_object = nullptr;
406
407  if (gobject)
408  {
409    if (gobject->funcs == &GlibmmIOChannel::vfunc_table)
410    {
411      cpp_object = reinterpret_cast<GlibmmIOChannel*>(gobject)->wrapper;
412
413      if (take_copy && cpp_object)
414        cpp_object->reference();
415    }
416    else
417    {
418      cpp_object = new ForeignIOChannel(gobject, take_copy);
419      cpp_object->reference(); // the refcount is initially 0
420    }
421  }
422
423  return Glib::RefPtr<IOChannel>(cpp_object);
424}
425
426/**** Glib::GlibmmIOChannel ************************************************/
427
428GIOStatus
429GlibmmIOChannel::io_read(
430  GIOChannel* channel, char* buf, gsize count, gsize* bytes_read, GError** err)
431{
432  const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper;
433
434  try
435  {
436    return (GIOStatus)wrapper->read_vfunc(buf, count, *bytes_read);
437  }
438  catch (Glib::Error& error)
439  {
440    error.propagate(err);
441  }
442  catch (...)
443  {
444    Glib::exception_handlers_invoke();
445  }
446
447  return G_IO_STATUS_ERROR;
448}
449
450GIOStatus
451GlibmmIOChannel::io_write(
452  GIOChannel* channel, const char* buf, gsize count, gsize* bytes_written, GError** err)
453{
454  const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper;
455
456  try
457  {
458    return (GIOStatus)wrapper->write_vfunc(buf, count, *bytes_written);
459  }
460  catch (Glib::Error& error)
461  {
462    error.propagate(err);
463  }
464  catch (...)
465  {
466    Glib::exception_handlers_invoke();
467  }
468
469  return G_IO_STATUS_ERROR;
470}
471
472GIOStatus
473GlibmmIOChannel::io_seek(GIOChannel* channel, gint64 offset, GSeekType type, GError** err)
474{
475  const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper;
476
477  try
478  {
479    return (GIOStatus)wrapper->seek_vfunc(offset, (SeekType)type);
480  }
481  catch (Glib::Error& error)
482  {
483    error.propagate(err);
484  }
485  catch (...)
486  {
487    Glib::exception_handlers_invoke();
488  }
489
490  return G_IO_STATUS_ERROR;
491}
492
493GIOStatus
494GlibmmIOChannel::io_close(GIOChannel* channel, GError** err)
495{
496  const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper;
497
498  try
499  {
500    return (GIOStatus)wrapper->close_vfunc();
501  }
502  catch (Glib::Error& error)
503  {
504    error.propagate(err);
505  }
506  catch (...)
507  {
508    Glib::exception_handlers_invoke();
509  }
510
511  return G_IO_STATUS_ERROR;
512}
513
514// static
515GSource*
516GlibmmIOChannel::io_create_watch(GIOChannel* channel, GIOCondition condition)
517{
518  const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper;
519
520  try
521  {
522    const auto source = wrapper->create_watch_vfunc((IOCondition)condition);
523    return (source) ? source->gobj_copy() : nullptr;
524  }
525  catch (...)
526  {
527    Glib::exception_handlers_invoke();
528  }
529
530  return nullptr;
531}
532
533// static
534void
535GlibmmIOChannel::io_free(GIOChannel* channel)
536{
537  if (IOChannel* const wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper)
538  {
539    wrapper->gobject_ = nullptr;
540    delete wrapper;
541  }
542
543  g_free(channel);
544}
545
546GIOStatus
547GlibmmIOChannel::io_set_flags(GIOChannel* channel, GIOFlags flags, GError** err)
548{
549  const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper;
550
551  try
552  {
553    return (GIOStatus)wrapper->set_flags_vfunc((IOFlags)flags);
554  }
555  catch (Glib::Error& error)
556  {
557    error.propagate(err);
558  }
559  catch (...)
560  {
561    Glib::exception_handlers_invoke();
562  }
563
564  return G_IO_STATUS_ERROR;
565}
566
567// static
568GIOFlags
569GlibmmIOChannel::io_get_flags(GIOChannel* channel)
570{
571  const auto wrapper = reinterpret_cast<GlibmmIOChannel*>(channel)->wrapper;
572
573  try
574  {
575    return (GIOFlags)wrapper->get_flags_vfunc();
576  }
577  catch (...)
578  {
579    Glib::exception_handlers_invoke();
580  }
581
582  return GIOFlags(0);
583}
584
585} // namespace Glib
586