jvm_dtrace.c revision 5776:de6a9e811145
1270114Sse/* 2270114Sse * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. 3270114Sse * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4270114Sse * 5270114Sse * This code is free software; you can redistribute it and/or modify it 6270114Sse * under the terms of the GNU General Public License version 2 only, as 7270114Sse * published by the Free Software Foundation. 8270114Sse * 9270114Sse * This code is distributed in the hope that it will be useful, but WITHOUT 10270114Sse * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11270114Sse * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12270114Sse * version 2 for more details (a copy is included in the LICENSE file that 13270114Sse * accompanied this code). 14270114Sse * 15270114Sse * You should have received a copy of the GNU General Public License version 16270114Sse * 2 along with this work; if not, write to the Free Software Foundation, 17270114Sse * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18322225Sse * 19270114Sse * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20270114Sse * or visit www.oracle.com if you need additional information or have any 21270114Sse * questions. 22270114Sse * 23283164Semaste */ 24270114Sse 25270114Sse#include <door.h> 26283164Semaste#include <errno.h> 27270114Sse#include <fcntl.h> 28270114Sse#include <limits.h> 29283164Semaste#include <poll.h> 30283164Semaste#include <signal.h> 31283164Semaste#include <stdarg.h> 32270114Sse#include <stdio.h> 33270114Sse#include <stdlib.h> 34322225Sse#include <string.h> 35270114Sse#include <sys/types.h> 36270114Sse#include <sys/stat.h> 37270114Sse#include <thread.h> 38270114Sse#include <unistd.h> 39270114Sse#include "jvm_dtrace.h" 40270114Sse 41270114Sse// NOTE: These constants are used in JVM code as well. 42270114Sse// KEEP JVM CODE IN SYNC if you are going to change these... 43270114Sse 44270114Sse#define DTRACE_ALLOC_PROBES 0x1 45270114Sse#define DTRACE_METHOD_PROBES 0x2 46270114Sse#define DTRACE_MONITOR_PROBES 0x4 47270114Sse#define DTRACE_ALL_PROBES -1 48270114Sse 49270114Sse// generic error messages 50270114Sse#define JVM_ERR_OUT_OF_MEMORY "out of memory (native heap)" 51270114Sse#define JVM_ERR_INVALID_PARAM "invalid input parameter(s)" 52270114Sse#define JVM_ERR_NULL_PARAM "input paramater is NULL" 53270114Sse 54270114Sse// error messages for attach 55270114Sse#define JVM_ERR_CANT_OPEN_DOOR "cannot open door file" 56270114Sse#define JVM_ERR_CANT_CREATE_ATTACH_FILE "cannot create attach file" 57270114Sse#define JVM_ERR_DOOR_FILE_PERMISSION "door file is not secure" 58270310Sse#define JVM_ERR_CANT_SIGNAL "cannot send SIGQUIT to target" 59270310Sse 60270114Sse// error messages for enable probe 61270310Sse#define JVM_ERR_DOOR_CMD_SEND "door command send failed" 62270310Sse#define JVM_ERR_DOOR_CANT_READ_STATUS "cannot read door command status" 63270310Sse#define JVM_ERR_DOOR_CMD_STATUS "door command error status" 64270310Sse 65270310Sse// error message for detach 66270114Sse#define JVM_ERR_CANT_CLOSE_DOOR "cannot close door file" 67270310Sse 68270310Sse#define RESTARTABLE(_cmd, _result) do { \ 69270310Sse do { \ 70270310Sse _result = _cmd; \ 71270310Sse } while((_result == -1) && (errno == EINTR)); \ 72270114Sse} while(0) 73270310Sse 74270310Ssestruct _jvm_t { 75270310Sse pid_t pid; 76270114Sse int door_fd; 77270114Sse}; 78270114Sse 79270114Ssestatic int libjvm_dtrace_debug; 80270114Ssestatic void print_debug(const char* fmt,...) { 81270114Sse if (libjvm_dtrace_debug) { 82270310Sse va_list alist; 83270310Sse va_start(alist, fmt); 84270310Sse fputs("libjvm_dtrace DEBUG: ", stderr); 85270310Sse vfprintf(stderr, fmt, alist); 86270114Sse va_end(alist); 87270310Sse } 88270310Sse} 89270114Sse 90270114Sse/* Key for thread local error message */ 91270114Ssestatic thread_key_t jvm_error_key; 92270114Sse 93270114Sse/* init function for this library */ 94270114Ssestatic void init_jvm_dtrace() { 95270114Sse /* check for env. var for debug mode */ 96270114Sse libjvm_dtrace_debug = getenv("LIBJVM_DTRACE_DEBUG") != NULL; 97270114Sse /* create key for thread local error message */ 98270114Sse if (thr_keycreate(&jvm_error_key, NULL) != 0) { 99270310Sse print_debug("can't create thread_key_t for jvm error key\n"); 100270310Sse // exit(1); ? 101270310Sse } 102270310Sse} 103270310Sse 104270114Sse#pragma init(init_jvm_dtrace) 105270114Sse 106270114Sse/* set thread local error message */ 107270114Ssestatic void set_jvm_error(const char* msg) { 108270114Sse thr_setspecific(jvm_error_key, (void*)msg); 109270114Sse} 110270114Sse 111270114Sse/* clear thread local error message */ 112270114Ssestatic void clear_jvm_error() { 113270114Sse thr_setspecific(jvm_error_key, NULL); 114270114Sse} 115270114Sse 116270114Sse/* file handling functions that can handle interrupt */ 117270114Sse 118270114Ssestatic int file_open(const char* path, int flag) { 119270114Sse int ret; 120270114Sse RESTARTABLE(open(path, flag), ret); 121270114Sse return ret; 122270114Sse} 123270114Sse 124270114Ssestatic int file_close(int fd) { 125283164Semaste return close(fd); 126270114Sse} 127270114Sse 128270114Ssestatic int file_read(int fd, char* buf, int len) { 129270114Sse int ret; 130270114Sse RESTARTABLE(read(fd, buf, len), ret); 131270114Sse return ret; 132270114Sse} 133270114Sse 134270114Sse/* send SIGQUIT signal to given process */ 135270114Ssestatic int send_sigquit(pid_t pid) { 136270114Sse int ret; 137270114Sse RESTARTABLE(kill(pid, SIGQUIT), ret); 138270114Sse return ret; 139270114Sse} 140270114Sse 141270114Sse/* called to check permissions on attach file */ 142270114Ssestatic int check_permission(const char* path) { 143270114Sse struct stat64 sb; 144270114Sse uid_t uid, gid; 145270114Sse int res; 146270114Sse 147270114Sse /* 148270114Sse * Check that the path is owned by the effective uid/gid of this 149270114Sse * process. Also check that group/other access is not allowed. 150270114Sse */ 151270114Sse uid = geteuid(); 152270114Sse gid = getegid(); 153270114Sse 154270114Sse res = stat64(path, &sb); 155270114Sse if (res != 0) { 156270114Sse print_debug("stat failed for %s\n", path); 157270114Sse return -1; 158270114Sse } 159270114Sse 160270114Sse if ((sb.st_uid != uid) || (sb.st_gid != gid) || 161270114Sse ((sb.st_mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0)) { 162270114Sse print_debug("well-known file %s is not secure\n", path); 163270114Sse return -1; 164270114Sse } 165270114Sse return 0; 166270114Sse} 167270114Sse 168270114Sse#define ATTACH_FILE_PATTERN "/tmp/.attach_pid%d" 169270114Sse 170270114Sse/* fill-in the name of attach file name in given buffer */ 171270114Ssestatic void fill_attach_file_name(char* path, int len, pid_t pid) { 172270114Sse memset(path, 0, len); 173270114Sse sprintf(path, ATTACH_FILE_PATTERN, pid); 174270114Sse} 175270114Sse 176270114Sse#define DOOR_FILE_PATTERN "/tmp/.java_pid%d" 177270114Sse 178270114Sse/* open door file for the given JVM */ 179270114Ssestatic int open_door(pid_t pid) { 180270114Sse char path[PATH_MAX + 1]; 181270114Sse int fd; 182270114Sse 183270114Sse sprintf(path, DOOR_FILE_PATTERN, pid); 184270114Sse fd = file_open(path, O_RDONLY); 185270114Sse if (fd < 0) { 186270114Sse set_jvm_error(JVM_ERR_CANT_OPEN_DOOR); 187270114Sse print_debug("cannot open door file %s\n", path); 188270114Sse return -1; 189270114Sse } 190270114Sse print_debug("opened door file %s\n", path); 191270114Sse if (check_permission(path) != 0) { 192270114Sse set_jvm_error(JVM_ERR_DOOR_FILE_PERMISSION); 193270114Sse print_debug("check permission failed for %s\n", path); 194270114Sse file_close(fd); 195270114Sse fd = -1; 196270114Sse } 197270114Sse return fd; 198270114Sse} 199270114Sse 200270114Sse/* create attach file for given process */ 201270310Ssestatic int create_attach_file(pid_t pid) { 202270114Sse char path[PATH_MAX + 1]; 203270310Sse int fd; 204270310Sse fill_attach_file_name(path, sizeof(path), pid); 205270310Sse fd = file_open(path, O_CREAT | O_RDWR); 206270310Sse if (fd < 0) { 207270310Sse set_jvm_error(JVM_ERR_CANT_CREATE_ATTACH_FILE); 208270310Sse print_debug("cannot create file %s\n", path); 209270114Sse } else { 210270114Sse print_debug("created attach file %s\n", path); 211270114Sse } 212270114Sse return fd; 213270114Sse} 214322225Sse 215270114Sse/* delete attach file for given process */ 216270114Ssestatic void delete_attach_file(pid_t pid) { 217270114Sse char path[PATH_MAX + 1]; 218270114Sse fill_attach_file_name(path, sizeof(path), pid); 219270114Sse int res = unlink(path); 220270114Sse if (res) { 221270114Sse print_debug("cannot delete attach file %s\n", path); 222270114Sse } else { 223270114Sse print_debug("deleted attach file %s\n", path); 224270310Sse } 225270310Sse} 226270310Sse 227270310Sse/* attach to given JVM */ 228270310Ssejvm_t* jvm_attach(pid_t pid) { 229270310Sse jvm_t* jvm; 230270310Sse int door_fd, attach_fd, i; 231270114Sse 232270114Sse jvm = (jvm_t*) calloc(1, sizeof(jvm_t)); 233270114Sse if (jvm == NULL) { 234270114Sse set_jvm_error(JVM_ERR_OUT_OF_MEMORY); 235270114Sse print_debug("calloc failed in %s at %d\n", __FILE__, __LINE__); 236270114Sse return NULL; 237270114Sse } 238270114Sse jvm->pid = pid; 239270114Sse attach_fd = -1; 240270114Sse 241270114Sse door_fd = open_door(pid); 242270114Sse if (door_fd < 0) { 243270114Sse print_debug("trying to create attach file\n"); 244270114Sse if ((attach_fd = create_attach_file(pid)) < 0) { 245270114Sse goto quit; 246270114Sse } 247270114Sse 248270114Sse /* send QUIT signal to the target so that it will 249270114Sse * check for the attach file. 250270114Sse */ 251270114Sse if (send_sigquit(pid) != 0) { 252270114Sse set_jvm_error(JVM_ERR_CANT_SIGNAL); 253270114Sse print_debug("sending SIGQUIT failed\n"); 254270114Sse goto quit; 255270114Sse } 256270114Sse 257270114Sse /* give the target VM time to start the attach mechanism */ 258270114Sse do { 259270114Sse int res; 260270114Sse RESTARTABLE(poll(0, 0, 200), res); 261270114Sse door_fd = open_door(pid); 262270114Sse i++; 263270114Sse } while (i <= 50 && door_fd == -1); 264270114Sse if (door_fd < 0) { 265270114Sse print_debug("Unable to open door to process %d\n", pid); 266270114Sse goto quit; 267270114Sse } 268270114Sse } 269270114Sse 270270114Ssequit: 271270114Sse if (attach_fd >= 0) { 272270114Sse file_close(attach_fd); 273270114Sse delete_attach_file(jvm->pid); 274270114Sse } 275270114Sse if (door_fd >= 0) { 276270114Sse jvm->door_fd = door_fd; 277270114Sse clear_jvm_error(); 278270114Sse } else { 279270114Sse free(jvm); 280270114Sse jvm = NULL; 281270114Sse } 282270114Sse return jvm; 283270114Sse} 284270114Sse 285270114Sse/* return the last thread local error message */ 286270114Sseconst char* jvm_get_last_error() { 287270114Sse const char* res = NULL; 288270114Sse thr_getspecific(jvm_error_key, (void**)&res); 289270114Sse return res; 290270114Sse} 291270114Sse 292270114Sse/* detach the givenb JVM */ 293270114Sseint jvm_detach(jvm_t* jvm) { 294270114Sse if (jvm) { 295270114Sse int res; 296270114Sse if (jvm->door_fd != -1) { 297270114Sse if (file_close(jvm->door_fd) != 0) { 298270114Sse set_jvm_error(JVM_ERR_CANT_CLOSE_DOOR); 299270114Sse res = -1; 300270114Sse } else { 301270114Sse clear_jvm_error(); 302270114Sse res = 0; 303270114Sse } 304270114Sse } 305270114Sse free(jvm); 306270114Sse return res; 307270114Sse } else { 308270114Sse set_jvm_error(JVM_ERR_NULL_PARAM); 309270114Sse print_debug("jvm_t* is NULL\n"); 310270114Sse return -1; 311270114Sse } 312270114Sse} 313270114Sse 314270114Sse/* 315270114Sse * A simple table to translate some known errors into reasonable 316270114Sse * error messages 317270114Sse */ 318270114Ssestatic struct { 319270114Sse int err; 320270114Sse const char* msg; 321270114Sse} const error_messages[] = { 322270114Sse { 100, "Bad request" }, 323270114Sse { 101, "Protocol mismatch" }, 324270114Sse { 102, "Resource failure" }, 325270114Sse { 103, "Internal error" }, 326270114Sse { 104, "Permission denied" }, 327270114Sse}; 328270114Sse 329270114Sse/* 330270114Sse * Lookup the given error code and return the appropriate 331270114Sse * message. If not found return NULL. 332270114Sse */ 333270114Ssestatic const char* translate_error(int err) { 334270114Sse int table_size = sizeof(error_messages) / sizeof(error_messages[0]); 335270114Sse int i; 336270114Sse 337270114Sse for (i=0; i<table_size; i++) { 338270114Sse if (err == error_messages[i].err) { 339270114Sse return error_messages[i].msg; 340270114Sse } 341270114Sse } 342270114Sse return NULL; 343270114Sse} 344270114Sse 345270114Sse/* 346270114Sse * Current protocol version 347270114Sse */ 348270114Ssestatic const char* PROTOCOL_VERSION = "1"; 349270114Sse 350270114Sse#define RES_BUF_SIZE 128 351270114Sse 352270114Sse/* 353270114Sse * Enqueue attach-on-demand command to the given JVM 354270114Sse */ 355270114Ssestatic 356270114Sseint enqueue_command(jvm_t* jvm, const char* cstr, int arg_count, const char** args) { 357270114Sse size_t size; 358270114Sse door_arg_t door_args; 359270114Sse char res_buffer[RES_BUF_SIZE]; 360270114Sse int rc, i; 361270114Sse char* buf = NULL; 362270114Sse int result = -1; 363270114Sse 364270114Sse /* 365270114Sse * First we get the command string and create the start of the 366270114Sse * argument string to send to the target VM: 367270114Sse * <ver>\0<cmd>\0 368270114Sse */ 369270114Sse if (cstr == NULL) { 370270114Sse print_debug("command name is NULL\n"); 371270114Sse goto quit; 372270114Sse } 373270114Sse size = strlen(PROTOCOL_VERSION) + strlen(cstr) + 2; 374270114Sse buf = (char*)malloc(size); 375270114Sse if (buf != NULL) { 376270114Sse char* pos = buf; 377270114Sse strcpy(buf, PROTOCOL_VERSION); 378270114Sse pos += strlen(PROTOCOL_VERSION)+1; 379270114Sse strcpy(pos, cstr); 380270114Sse } else { 381270114Sse set_jvm_error(JVM_ERR_OUT_OF_MEMORY); 382270114Sse print_debug("malloc failed at %d in %s\n", __LINE__, __FILE__); 383270114Sse goto quit; 384270114Sse } 385270114Sse 386270114Sse /* 387270114Sse * Next we iterate over the arguments and extend the buffer 388270114Sse * to include them. 389270114Sse */ 390270114Sse for (i=0; i<arg_count; i++) { 391270114Sse cstr = args[i]; 392270114Sse if (cstr != NULL) { 393270114Sse size_t len = strlen(cstr); 394270114Sse char* newbuf = (char*)realloc(buf, size+len+1); 395270114Sse if (newbuf == NULL) { 396270114Sse set_jvm_error(JVM_ERR_OUT_OF_MEMORY); 397270114Sse print_debug("realloc failed in %s at %d\n", __FILE__, __LINE__); 398270114Sse goto quit; 399270114Sse } 400270114Sse buf = newbuf; 401270114Sse strcpy(buf+size, cstr); 402270114Sse size += len+1; 403270114Sse } 404270114Sse } 405270114Sse 406270114Sse /* 407270114Sse * The arguments to the door function are in 'buf' so we now 408270114Sse * do the door call 409270114Sse */ 410270114Sse door_args.data_ptr = buf; 411270114Sse door_args.data_size = size; 412270114Sse door_args.desc_ptr = NULL; 413270114Sse door_args.desc_num = 0; 414270114Sse door_args.rbuf = (char*)&res_buffer; 415270114Sse door_args.rsize = sizeof(res_buffer); 416270114Sse 417270114Sse RESTARTABLE(door_call(jvm->door_fd, &door_args), rc); 418270114Sse 419270114Sse /* 420270114Sse * door_call failed 421270114Sse */ 422270114Sse if (rc == -1) { 423270114Sse print_debug("door_call failed\n"); 424270114Sse } else { 425270114Sse /* 426270114Sse * door_call succeeded but the call didn't return the the expected jint. 427270114Sse */ 428270114Sse if (door_args.data_size < sizeof(int)) { 429270114Sse print_debug("Enqueue error - reason unknown as result is truncated!"); 430270114Sse } else { 431270114Sse int* res = (int*)(door_args.data_ptr); 432270114Sse if (*res != 0) { 433270114Sse const char* msg = translate_error(*res); 434270114Sse if (msg == NULL) { 435270114Sse print_debug("Unable to enqueue command to target VM: %d\n", *res); 436270114Sse } else { 437270114Sse print_debug("Unable to enqueue command to target VM: %s\n", msg); 438270114Sse } 439270114Sse } else { 440270114Sse /* 441270114Sse * The door call should return a file descriptor to one end of 442270114Sse * a socket pair 443270114Sse */ 444270114Sse if ((door_args.desc_ptr != NULL) && 445270114Sse (door_args.desc_num == 1) && 446270114Sse (door_args.desc_ptr->d_attributes & DOOR_DESCRIPTOR)) { 447270114Sse result = door_args.desc_ptr->d_data.d_desc.d_descriptor; 448270114Sse } else { 449270114Sse print_debug("Reply from enqueue missing descriptor!\n"); 450270114Sse } 451270114Sse } 452270114Sse } 453270114Sse } 454270114Sse 455270114Ssequit: 456270114Sse if (buf) free(buf); 457270114Sse return result; 458270114Sse} 459270114Sse 460270114Sse/* read status code for a door command */ 461270114Ssestatic int read_status(int fd) { 462270114Sse char ch, buf[16]; 463270114Sse int index = 0; 464270114Sse 465270114Sse while (1) { 466270114Sse if (file_read(fd, &ch, sizeof(ch)) != sizeof(ch)) { 467270114Sse set_jvm_error(JVM_ERR_DOOR_CANT_READ_STATUS); 468270114Sse print_debug("door cmd status: read status failed\n"); 469270114Sse return -1; 470270114Sse } 471270114Sse buf[index++] = ch; 472270114Sse if (ch == '\n') { 473270114Sse buf[index - 1] = '\0'; 474270114Sse return atoi(buf); 475270114Sse } 476270114Sse if (index == sizeof(buf)) { 477270114Sse set_jvm_error(JVM_ERR_DOOR_CANT_READ_STATUS); 478270114Sse print_debug("door cmd status: read status overflow\n"); 479270114Sse return -1; 480270114Sse } 481270114Sse } 482270114Sse} 483270114Sse 484270114Ssestatic const char* ENABLE_DPROBES_CMD = "enabledprobes"; 485270114Sse 486270114Sse/* enable one or more DTrace probes for a given JVM */ 487270114Sseint jvm_enable_dtprobes(jvm_t* jvm, int num_probe_types, const char** probe_types) { 488270114Sse int fd, status = 0; 489270114Sse char ch; 490270114Sse const char* args[1]; 491270114Sse char buf[16]; 492270114Sse int probe_type = 0, index; 493270114Sse int count = 0; 494270114Sse 495270114Sse if (jvm == NULL) { 496270114Sse set_jvm_error(JVM_ERR_NULL_PARAM); 497270114Sse print_debug("jvm_t* is NULL\n"); 498270114Sse return -1; 499270114Sse } 500270114Sse 501270114Sse if (num_probe_types == 0 || probe_types == NULL || 502270114Sse probe_types[0] == NULL) { 503270114Sse set_jvm_error(JVM_ERR_INVALID_PARAM); 504270114Sse print_debug("invalid probe type argument(s)\n"); 505270114Sse return -1; 506270114Sse } 507322225Sse 508322225Sse for (index = 0; index < num_probe_types; index++) { 509322225Sse const char* p = probe_types[index]; 510322225Sse if (strcmp(p, JVM_DTPROBE_OBJECT_ALLOC) == 0) { 511322225Sse probe_type |= DTRACE_ALLOC_PROBES; 512270114Sse count++; 513322225Sse } else if (strcmp(p, JVM_DTPROBE_METHOD_ENTRY) == 0 || 514322225Sse strcmp(p, JVM_DTPROBE_METHOD_RETURN) == 0) { 515322225Sse probe_type |= DTRACE_METHOD_PROBES; 516322225Sse count++; 517322225Sse } else if (strcmp(p, JVM_DTPROBE_MONITOR_ENTER) == 0 || 518322225Sse strcmp(p, JVM_DTPROBE_MONITOR_ENTERED) == 0 || 519270114Sse strcmp(p, JVM_DTPROBE_MONITOR_EXIT) == 0 || 520270114Sse strcmp(p, JVM_DTPROBE_MONITOR_WAIT) == 0 || 521270114Sse strcmp(p, JVM_DTPROBE_MONITOR_WAITED) == 0 || 522270114Sse strcmp(p, JVM_DTPROBE_MONITOR_NOTIFY) == 0 || 523270114Sse strcmp(p, JVM_DTPROBE_MONITOR_NOTIFYALL) == 0) { 524270114Sse probe_type |= DTRACE_MONITOR_PROBES; 525270114Sse count++; 526270114Sse } else if (strcmp(p, JVM_DTPROBE_ALL) == 0) { 527322225Sse probe_type |= DTRACE_ALL_PROBES; 528322225Sse count++; 529322225Sse } 530270114Sse } 531270114Sse 532270114Sse if (count == 0) { 533270114Sse return count; 534270114Sse } 535270114Sse sprintf(buf, "%d", probe_type); 536270114Sse args[0] = buf; 537270114Sse 538270114Sse fd = enqueue_command(jvm, ENABLE_DPROBES_CMD, 1, args); 539270114Sse if (fd < 0) { 540270114Sse set_jvm_error(JVM_ERR_DOOR_CMD_SEND); 541270114Sse return -1; 542270114Sse } 543270114Sse 544270114Sse status = read_status(fd); 545270114Sse // non-zero status is error 546270114Sse if (status) { 547270114Sse set_jvm_error(JVM_ERR_DOOR_CMD_STATUS); 548270114Sse print_debug("%s command failed (status: %d) in target JVM\n", 549270114Sse ENABLE_DPROBES_CMD, status); 550270114Sse file_close(fd); 551270114Sse return -1; 552270114Sse } 553270114Sse // read from stream until EOF 554270114Sse while (file_read(fd, &ch, sizeof(ch)) == sizeof(ch)) { 555270114Sse if (libjvm_dtrace_debug) { 556270114Sse printf("%c", ch); 557270114Sse } 558270114Sse } 559270114Sse 560270114Sse file_close(fd); 561270114Sse clear_jvm_error(); 562270114Sse return count; 563270114Sse} 564270114Sse