pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Route GLib debug logging directly to the Finch debug window
2021-10-18, Elliott Sales de Andrade
1896a80ff8e3
Route GLib debug logging directly to the Finch debug window
Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.
Testing Done:
Open the Debug window an see some `GLib-*` outputs.
Reviewed at https://reviews.imfreedom.org/r/1057/
/*
* Purple - Internet Messaging Library
* Copyright (C) Pidgin Developers <devel@pidgin.im>
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include
<glib/gi18n-lib.h>
#include
"zephyr_tzc.h"
#define MAXCHILDREN 20
typedef
gssize
(
*
PollableInputStreamReadFunc
)(
GPollableInputStream
*
stream
,
void
*
bufcur
,
GError
**
error
);
static
gboolean
tzc_write
(
zephyr_account
*
zephyr
,
const
gchar
*
format
,
...)
G_GNUC_PRINTF
(
2
,
3
);
static
gchar
*
tzc_read
(
zephyr_account
*
zephyr
,
PollableInputStreamReadFunc
read_func
)
{
GPollableInputStream
*
stream
=
G_POLLABLE_INPUT_STREAM
(
zephyr
->
tzc_stdout
);
gsize
bufsize
=
2048
;
gchar
*
buf
=
g_new
(
gchar
,
bufsize
);
gchar
*
bufcur
=
buf
;
gboolean
selected
=
FALSE
;
while
(
TRUE
)
{
GError
*
error
=
NULL
;
if
(
read_func
(
stream
,
bufcur
,
&
error
)
<
0
)
{
if
(
error
->
code
==
G_IO_ERROR_WOULD_BLOCK
||
error
->
code
==
G_IO_ERROR_TIMED_OUT
)
{
g_error_free
(
error
);
break
;
}
purple_debug_error
(
"zephyr"
,
"couldn't read: %s"
,
error
->
message
);
purple_connection_error
(
purple_account_get_connection
(
zephyr
->
account
),
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
"couldn't read"
);
g_error_free
(
error
);
g_free
(
buf
);
return
NULL
;
}
selected
=
TRUE
;
bufcur
++
;
if
((
bufcur
-
buf
)
>
(
bufsize
-
1
))
{
if
((
buf
=
g_realloc
(
buf
,
bufsize
*
2
))
==
NULL
)
{
purple_debug_error
(
"zephyr"
,
"Ran out of memory
\n
"
);
exit
(
-1
);
}
else
{
bufcur
=
buf
+
bufsize
;
bufsize
*=
2
;
}
}
}
*
bufcur
=
'\0'
;
if
(
!
selected
)
{
g_free
(
buf
);
buf
=
NULL
;
}
return
buf
;
}
static
gboolean
tzc_write
(
zephyr_account
*
zephyr
,
const
gchar
*
format
,
...)
{
va_list
args
;
gchar
*
message
;
GError
*
error
=
NULL
;
gboolean
success
;
va_start
(
args
,
format
);
message
=
g_strdup_vprintf
(
format
,
args
);
va_end
(
args
);
success
=
g_output_stream_write_all
(
zephyr
->
tzc_stdin
,
message
,
strlen
(
message
),
NULL
,
NULL
,
&
error
);
if
(
!
success
)
{
purple_debug_error
(
"zephyr"
,
"Unable to write a message: %s"
,
error
->
message
);
g_error_free
(
error
);
}
g_free
(
message
);
return
success
;
}
/* Munge the outgoing zephyr so that any quotes or backslashes are
escaped and do not confuse tzc: */
static
char
*
tzc_escape_msg
(
const
char
*
message
)
{
gsize
msglen
;
char
*
newmsg
;
if
(
!
message
||
!*
message
)
{
return
g_strdup
(
""
);
}
msglen
=
strlen
(
message
);
newmsg
=
g_new0
(
char
,
msglen
*
2
+
1
);
for
(
gsize
pos
=
0
,
pos2
=
0
;
pos
<
msglen
;
pos
++
,
pos2
++
)
{
if
(
message
[
pos
]
==
'\\'
||
message
[
pos
]
==
'"'
)
{
newmsg
[
pos2
]
=
'\\'
;
pos2
++
;
}
newmsg
[
pos2
]
=
message
[
pos
];
}
return
newmsg
;
}
static
char
*
tzc_deescape_str
(
const
char
*
message
)
{
gsize
msglen
;
char
*
newmsg
;
if
(
!
message
||
!*
message
)
{
return
g_strdup
(
""
);
}
msglen
=
strlen
(
message
);
newmsg
=
g_new0
(
char
,
msglen
+
1
);
for
(
gsize
pos
=
0
,
pos2
=
0
;
pos
<
msglen
;
pos
++
,
pos2
++
)
{
if
(
message
[
pos
]
==
'\\'
)
{
pos
++
;
}
newmsg
[
pos2
]
=
message
[
pos
];
}
return
newmsg
;
}
static
GSubprocess
*
get_tzc_process
(
const
zephyr_account
*
zephyr
)
{
GSubprocess
*
tzc_process
=
NULL
;
const
gchar
*
tzc_cmd
;
gchar
**
tzc_cmd_array
=
NULL
;
GError
*
error
=
NULL
;
gboolean
found_ps
=
FALSE
;
gint
i
;
/* tzc_command should really be of the form
path/to/tzc -e %s
or
ssh username@hostname pathtotzc -e %s
-- this should not require a password, and ideally should be
kerberized ssh --
or
fsh username@hostname pathtotzc -e %s
*/
tzc_cmd
=
purple_account_get_string
(
zephyr
->
account
,
"tzc_command"
,
"/usr/bin/tzc -e %s"
);
if
(
!
g_shell_parse_argv
(
tzc_cmd
,
NULL
,
&
tzc_cmd_array
,
&
error
))
{
purple_debug_error
(
"zephyr"
,
"Unable to parse tzc_command: %s"
,
error
->
message
);
purple_connection_error
(
purple_account_get_connection
(
zephyr
->
account
),
PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
"invalid tzc_command setting"
);
g_error_free
(
error
);
return
NULL
;
}
for
(
i
=
0
;
tzc_cmd_array
[
i
]
!=
NULL
;
i
++
)
{
if
(
purple_strequal
(
tzc_cmd_array
[
i
],
"%s"
))
{
g_free
(
tzc_cmd_array
[
i
]);
tzc_cmd_array
[
i
]
=
g_strdup
(
zephyr
->
exposure
);
found_ps
=
TRUE
;
}
}
if
(
!
found_ps
)
{
purple_debug_error
(
"zephyr"
,
"tzc exited early"
);
purple_connection_error
(
purple_account_get_connection
(
zephyr
->
account
),
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
"invalid output by tzc (or bad parsing code)"
);
g_strfreev
(
tzc_cmd_array
);
return
NULL
;
}
tzc_process
=
g_subprocess_newv
(
(
const
gchar
*
const
*
)
tzc_cmd_array
,
G_SUBPROCESS_FLAGS_STDIN_PIPE
|
G_SUBPROCESS_FLAGS_STDOUT_PIPE
,
&
error
);
if
(
tzc_process
==
NULL
)
{
purple_debug_error
(
"zephyr"
,
"tzc exited early: %s"
,
error
->
message
);
purple_connection_error
(
purple_account_get_connection
(
zephyr
->
account
),
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
"invalid output by tzc (or bad parsing code)"
);
g_error_free
(
error
);
}
g_strfreev
(
tzc_cmd_array
);
return
tzc_process
;
}
static
gssize
pollable_input_stream_read
(
GPollableInputStream
*
stream
,
void
*
bufcur
,
GError
**
error
)
{
return
g_pollable_input_stream_read_nonblocking
(
stream
,
bufcur
,
1
,
NULL
,
error
);
}
static
gssize
pollable_input_stream_read_with_timeout
(
GPollableInputStream
*
stream
,
void
*
bufcur
,
GError
**
error
)
{
const
gint64
timeout
=
10
*
G_USEC_PER_SEC
;
gint64
now
=
g_get_monotonic_time
();
while
(
g_get_monotonic_time
()
<
now
+
timeout
)
{
GError
*
local_error
=
NULL
;
gssize
ret
=
g_pollable_input_stream_read_nonblocking
(
stream
,
bufcur
,
1
,
NULL
,
&
local_error
);
if
(
ret
==
1
)
{
return
ret
;
}
if
(
local_error
->
code
!=
G_IO_ERROR_WOULD_BLOCK
)
{
g_propagate_error
(
error
,
local_error
);
return
ret
;
}
/* Keep on waiting if this is a blocking error. */
g_clear_error
(
&
local_error
);
}
g_set_error_literal
(
error
,
G_IO_ERROR
,
G_IO_ERROR_TIMED_OUT
,
"tzc did not respond in time"
);
return
-1
;
}
static
gint
get_paren_level
(
gint
paren_level
,
gchar
ch
)
{
switch
(
ch
)
{
case
'('
:
return
paren_level
+
1
;
case
')'
:
return
paren_level
-
1
;
default
:
return
paren_level
;
}
}
static
void
parse_tzc_login_data
(
zephyr_account
*
zephyr
,
const
gchar
*
buf
,
gint
buflen
)
{
gchar
*
str
=
g_strndup
(
buf
,
buflen
);
purple_debug_info
(
"zephyr"
,
"tempstr parsed"
);
/* str should now be a string containing all characters from
* buf after the first ( to the one before the last paren ).
* We should have the following possible lisp strings but we don't care
* (tzcspew . start) (version . "something") (pid . number)
* We care about 'zephyrid . "username@REALM.NAME"' and
* 'exposure . "SOMETHING"' */
if
(
!
g_ascii_strncasecmp
(
str
,
"zephyrid"
,
8
))
{
gchar
**
strv
;
gchar
*
username
;
const
char
*
at
;
purple_debug_info
(
"zephyr"
,
"zephyrid found"
);
strv
=
g_strsplit
(
str
+
8
,
"
\"
"
,
-1
);
username
=
strv
[
1
]
?
strv
[
1
]
:
""
;
zephyr
->
username
=
g_strdup
(
username
);
at
=
strchr
(
username
,
'@'
);
if
(
at
!=
NULL
)
{
zephyr
->
realm
=
g_strdup
(
at
+
1
);
}
else
{
zephyr
->
realm
=
get_zephyr_realm
(
zephyr
->
account
,
"local-realm"
);
}
g_strfreev
(
strv
);
}
else
{
purple_debug_info
(
"zephyr"
,
"something that's not zephyr id found %s"
,
str
);
}
/* We don't care about anything else yet */
g_free
(
str
);
}
static
gchar
*
tree_child_contents
(
GNode
*
tree
,
int
index
)
{
GNode
*
child
=
g_node_nth_child
(
tree
,
index
);
return
child
?
child
->
data
:
""
;
}
static
GNode
*
find_node
(
GNode
*
ptree
,
gchar
*
key
)
{
guint
num_children
;
gchar
*
tc
;
if
(
!
ptree
||
!
key
)
return
NULL
;
num_children
=
g_node_n_children
(
ptree
);
tc
=
tree_child_contents
(
ptree
,
0
);
/* g_strcasecmp() is deprecated. What is the encoding here??? */
if
(
num_children
>
0
&&
tc
&&
!
g_ascii_strcasecmp
(
tc
,
key
))
{
return
ptree
;
}
else
{
GNode
*
result
=
NULL
;
guint
i
;
for
(
i
=
0
;
i
<
num_children
;
i
++
)
{
result
=
find_node
(
g_node_nth_child
(
ptree
,
i
),
key
);
if
(
result
!=
NULL
)
{
break
;
}
}
return
result
;
}
}
static
GNode
*
parse_buffer
(
const
gchar
*
source
,
gboolean
do_parse
)
{
GNode
*
ptree
=
g_node_new
(
NULL
);
if
(
do_parse
)
{
unsigned
int
p
=
0
;
while
(
p
<
strlen
(
source
))
{
unsigned
int
end
;
gchar
*
newstr
;
/* Eat white space: */
if
(
g_ascii_isspace
(
source
[
p
])
||
source
[
p
]
==
'\001'
)
{
p
++
;
continue
;
}
/* Skip comments */
if
(
source
[
p
]
==
';'
)
{
while
(
source
[
p
]
!=
'\n'
&&
p
<
strlen
(
source
))
{
p
++
;
}
continue
;
}
if
(
source
[
p
]
==
'('
)
{
int
nesting
=
0
;
gboolean
in_quote
=
FALSE
;
gboolean
escape_next
=
FALSE
;
p
++
;
end
=
p
;
while
(
!
(
source
[
end
]
==
')'
&&
nesting
==
0
&&
!
in_quote
)
&&
end
<
strlen
(
source
))
{
if
(
!
escape_next
)
{
if
(
source
[
end
]
==
'\\'
)
{
escape_next
=
TRUE
;
}
if
(
!
in_quote
)
{
nesting
=
get_paren_level
(
nesting
,
source
[
end
]);
}
if
(
source
[
end
]
==
'"'
)
{
in_quote
=
!
in_quote
;
}
}
else
{
escape_next
=
FALSE
;
}
end
++
;
}
do_parse
=
TRUE
;
}
else
{
gchar
end_char
;
if
(
source
[
p
]
==
'"'
)
{
end_char
=
'"'
;
p
++
;
}
else
{
end_char
=
' '
;
}
do_parse
=
FALSE
;
end
=
p
;
while
(
source
[
end
]
!=
end_char
&&
end
<
strlen
(
source
))
{
if
(
source
[
end
]
==
'\\'
)
end
++
;
end
++
;
}
}
newstr
=
g_new0
(
gchar
,
end
+
1
-
p
);
strncpy
(
newstr
,
source
+
p
,
end
-
p
);
if
(
g_node_n_children
(
ptree
)
<
MAXCHILDREN
)
{
/* In case we surpass maxchildren, ignore this */
g_node_append
(
ptree
,
parse_buffer
(
newstr
,
do_parse
));
}
else
{
purple_debug_error
(
"zephyr"
,
"too many children in tzc output. skipping
\n
"
);
}
g_free
(
newstr
);
p
=
end
+
1
;
}
}
else
{
/* XXX does this have to be strdup'd */
ptree
->
data
=
g_strdup
(
source
);
}
return
ptree
;
}
gboolean
tzc_login
(
zephyr_account
*
zephyr
)
{
gchar
*
buf
=
NULL
;
const
gchar
*
bufend
=
NULL
;
const
gchar
*
ptr
;
const
gchar
*
tmp
;
gint
parenlevel
=
0
;
zephyr
->
tzc_proc
=
get_tzc_process
(
zephyr
);
if
(
zephyr
->
tzc_proc
==
NULL
)
{
return
FALSE
;
}
zephyr
->
tzc_stdin
=
g_subprocess_get_stdin_pipe
(
zephyr
->
tzc_proc
);
zephyr
->
tzc_stdout
=
g_subprocess_get_stdout_pipe
(
zephyr
->
tzc_proc
);
purple_debug_info
(
"zephyr"
,
"about to read from tzc"
);
buf
=
tzc_read
(
zephyr
,
pollable_input_stream_read_with_timeout
);
if
(
buf
==
NULL
)
{
return
FALSE
;
}
bufend
=
buf
+
strlen
(
buf
);
ptr
=
buf
;
/* ignore all tzcoutput till we've received the first ( */
while
(
ptr
<
bufend
&&
(
*
ptr
!=
'('
))
{
ptr
++
;
}
if
(
ptr
>=
bufend
)
{
purple_connection_error
(
purple_account_get_connection
(
zephyr
->
account
),
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
"invalid output by tzc (or bad parsing code)"
);
g_free
(
buf
);
return
FALSE
;
}
do
{
parenlevel
=
get_paren_level
(
parenlevel
,
*
ptr
);
purple_debug_info
(
"zephyr"
,
"tzc parenlevel is %d"
,
parenlevel
);
switch
(
parenlevel
)
{
case
1
:
/* Search for next beginning (, or for the ending */
do
{
ptr
++
;
}
while
((
ptr
<
bufend
)
&&
(
*
ptr
!=
'('
)
&&
(
*
ptr
!=
')'
));
if
(
ptr
>=
bufend
)
{
purple_debug_error
(
"zephyr"
,
"tzc parsing error"
);
}
break
;
case
2
:
/* You are probably at
(foo . bar ) or (foo . "bar") or (foo . chars) or (foo . numbers) or (foo . () )
Parse all the data between the first and last f, and move past )
*/
tmp
=
ptr
;
do
{
ptr
++
;
parenlevel
=
get_paren_level
(
parenlevel
,
*
ptr
);
}
while
(
parenlevel
>
1
);
parse_tzc_login_data
(
zephyr
,
tmp
+
1
,
ptr
-
tmp
);
ptr
++
;
break
;
default
:
purple_debug_info
(
"zephyr"
,
"parenlevel is not 1 or 2"
);
/* This shouldn't be happening */
break
;
}
}
while
(
ptr
<
bufend
&&
parenlevel
!=
0
);
purple_debug_info
(
"zephyr"
,
"tzc startup done"
);
g_free
(
buf
);
return
TRUE
;
}
gint
tzc_check_notify
(
gpointer
data
)
{
PurpleConnection
*
gc
=
(
PurpleConnection
*
)
data
;
zephyr_account
*
zephyr
=
purple_connection_get_protocol_data
(
gc
);
GNode
*
newparsetree
=
NULL
;
gchar
*
buf
=
tzc_read
(
zephyr
,
pollable_input_stream_read
);
if
(
buf
!=
NULL
)
{
newparsetree
=
parse_buffer
(
buf
,
TRUE
);
g_free
(
buf
);
}
if
(
newparsetree
!=
NULL
)
{
gchar
*
spewtype
;
if
(
(
spewtype
=
tree_child_contents
(
find_node
(
newparsetree
,
"tzcspew"
),
2
))
)
{
if
(
!
g_ascii_strncasecmp
(
spewtype
,
"message"
,
7
))
{
ZNotice_t
notice
;
GNode
*
msgnode
=
g_node_nth_child
(
find_node
(
newparsetree
,
"message"
),
2
);
/*char *zsig = g_strdup(" ");*/
/* purple doesn't care about zsigs */
char
*
msg
=
tzc_deescape_str
(
tree_child_contents
(
msgnode
,
1
));
char
*
buf
=
g_strdup_printf
(
" %c%s"
,
'\0'
,
msg
);
memset
((
char
*
)
&
notice
,
0
,
sizeof
(
notice
));
notice
.
z_kind
=
ACKED
;
notice
.
z_port
=
0
;
notice
.
z_opcode
=
tree_child_contents
(
find_node
(
newparsetree
,
"opcode"
),
2
);
notice
.
z_class
=
tzc_deescape_str
(
tree_child_contents
(
find_node
(
newparsetree
,
"class"
),
2
));
notice
.
z_class_inst
=
tree_child_contents
(
find_node
(
newparsetree
,
"instance"
),
2
);
notice
.
z_recipient
=
zephyr_normalize_local_realm
(
zephyr
,
tree_child_contents
(
find_node
(
newparsetree
,
"recipient"
),
2
));
notice
.
z_sender
=
zephyr_normalize_local_realm
(
zephyr
,
tree_child_contents
(
find_node
(
newparsetree
,
"sender"
),
2
));
notice
.
z_default_format
=
"Class $class, Instance $instance:
\n
"
"To: @bold($recipient) at $time $date
\n
"
"From: @bold($1) <$sender>
\n\n
$2"
;
notice
.
z_message_len
=
1
+
1
+
strlen
(
msg
)
+
1
;
notice
.
z_message
=
buf
;
handle_message
(
gc
,
&
notice
);
g_free
(
msg
);
/*g_free(zsig);*/
g_free
(
buf
);
}
else
if
(
!
g_ascii_strncasecmp
(
spewtype
,
"zlocation"
,
9
))
{
/* check_loc or zephyr_zloc respectively */
/* XXX fix */
GNode
*
locations
=
g_node_nth_child
(
g_node_nth_child
(
find_node
(
newparsetree
,
"locations"
),
2
),
0
);
ZLocations_t
zloc
=
{
.
host
=
tree_child_contents
(
g_node_nth_child
(
locations
,
0
),
2
),
.
time
=
tree_child_contents
(
g_node_nth_child
(
locations
,
2
),
2
),
.
tty
=
NULL
};
handle_locations
(
gc
,
tree_child_contents
(
find_node
(
newparsetree
,
"user"
),
2
),
(
zloc
.
host
&&
*
zloc
.
host
&&
!
purple_strequal
(
zloc
.
host
,
" "
))
?
1
:
0
,
&
zloc
);
}
else
if
(
!
g_ascii_strncasecmp
(
spewtype
,
"subscribed"
,
10
))
{
}
else
if
(
!
g_ascii_strncasecmp
(
spewtype
,
"start"
,
5
))
{
}
else
if
(
!
g_ascii_strncasecmp
(
spewtype
,
"error"
,
5
))
{
/* XXX handle */
}
}
else
{
}
}
else
{
}
g_node_destroy
(
newparsetree
);
return
TRUE
;
}
gboolean
tzc_subscribe_to
(
zephyr_account
*
zephyr
,
ZSubscription_t
*
sub
)
{
/* ((tzcfodder . subscribe) ("class" "instance" "recipient")) */
return
tzc_write
(
zephyr
,
"((tzcfodder . subscribe) (
\"
%s
\"
\"
%s
\"
\"
%s
\"
))
\n
"
,
sub
->
zsub_class
,
sub
->
zsub_classinst
,
sub
->
zsub_recipient
);
}
gboolean
tzc_request_locations
(
zephyr_account
*
zephyr
,
gchar
*
who
)
{
return
tzc_write
(
zephyr
,
"((tzcfodder . zlocate)
\"
%s
\"
)
\n
"
,
who
);
}
gboolean
tzc_send_message
(
zephyr_account
*
zephyr
,
gchar
*
zclass
,
gchar
*
instance
,
gchar
*
recipient
,
const
gchar
*
html_buf
,
const
gchar
*
sig
,
G_GNUC_UNUSED
const
gchar
*
opcode
)
{
/* CMU cclub tzc doesn't grok opcodes for now */
char
*
tzc_sig
=
tzc_escape_msg
(
sig
);
char
*
tzc_body
=
tzc_escape_msg
(
html_buf
);
gboolean
result
;
result
=
tzc_write
(
zephyr
,
"((tzcfodder . send) (class .
\"
%s
\"
) (auth . t) (recipients (
\"
%s
\"
.
\"
%s
\"
)) (message . (
\"
%s
\"
\"
%s
\"
)) )
\n
"
,
zclass
,
instance
,
recipient
,
tzc_sig
,
tzc_body
);
g_free
(
tzc_sig
);
g_free
(
tzc_body
);
return
result
;
}
void
tzc_set_location
(
zephyr_account
*
zephyr
,
char
*
exposure
)
{
tzc_write
(
zephyr
,
"((tzcfodder . set-location) (hostname .
\"
%s
\"
) (exposure .
\"
%s
\"
))
\n
"
,
zephyr
->
ourhost
,
exposure
);
}
void
tzc_get_subs_from_server
(
G_GNUC_UNUSED
zephyr_account
*
zephyr
,
PurpleConnection
*
gc
)
{
/* XXX fix */
purple_notify_error
(
gc
,
""
,
"tzc doesn't support this action"
,
NULL
,
purple_request_cpar_from_connection
(
gc
));
}
void
tzc_close
(
zephyr_account
*
zephyr
)
{
#ifdef G_OS_UNIX
GError
*
error
=
NULL
;
g_subprocess_send_signal
(
zephyr
->
tzc_proc
,
SIGTERM
);
if
(
!
g_subprocess_wait
(
zephyr
->
tzc_proc
,
NULL
,
&
error
))
{
purple_debug_error
(
"zephyr"
,
"error while attempting to close tzc: %s"
,
error
->
message
);
g_error_free
(
error
);
}
#else
g_subprocess_force_exit
(
zephyr
->
tzc_proc
);
#endif
zephyr
->
tzc_stdin
=
NULL
;
zephyr
->
tzc_stdout
=
NULL
;
g_clear_object
(
&
zephyr
->
tzc_proc
);
}