1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
27 #include <android/log.h>
31 #include <osl/detail/android-bootstrap.h>
35 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "lo-bootstrap", __VA_ARGS__))
36 #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "lo-bootstrap", __VA_ARGS__))
38 #define MAX(a,b) ((a) > (b) ? (a) : (b))
40 // TODO: workaround for unified headers migration - only made available when
41 // __USE_BSD or __BIONIC__ are defined, so just add those here...
42 #define letoh16(x) (x)
43 #define letoh32(x) (x)
49 /* These are valid / used in all apps. */
51 const char *cache_dir
;
56 /* Zip data structures */
58 /* compression methods */
61 struct local_file_header
{
64 uint16_t general_flag
;
66 uint16_t lastmod_time
;
67 uint16_t lastmod_date
;
69 uint32_t compressed_size
;
70 uint32_t uncompressed_size
;
71 uint16_t filename_size
;
72 uint16_t extra_field_size
;
74 } __attribute__((__packed__
));
78 uint16_t creator_version
;
80 uint16_t general_flag
;
82 uint16_t lastmod_time
;
83 uint16_t lastmod_date
;
85 uint32_t compressed_size
;
86 uint32_t uncompressed_size
;
87 uint16_t filename_size
;
88 uint16_t extra_field_size
;
89 uint16_t file_comment_size
;
91 uint16_t internal_attr
;
92 uint32_t external_attr
;
95 } __attribute__((__packed__
));
97 #define CDIR_END_SIG 0x06054b50
103 uint16_t disk_entries
;
104 uint16_t cdir_entries
;
106 uint32_t cdir_offset
;
107 uint16_t comment_size
;
109 } __attribute__((__packed__
));
111 /* End of Zip data structures */
113 static struct cdir_entry
*cdir_start
;
114 static uint16_t cdir_entries
;
116 /* Data structure to turn Zip's list in arbitrary order of
117 * hierarchical pathnames (not necessarily including entries for
118 * directories) into an actual hierarchical directory tree, so that we
119 * can iterate over directory entries properly in the dirent style
123 typedef struct direntry
*direntry
;
127 enum { REGULAR
, DIRECTORY
} kind
;
130 struct cdir_entry
*file
;
139 static direntry assets
= NULL
;
142 cdir_entry_size(struct cdir_entry
*entry
)
144 return sizeof(*entry
) +
145 letoh16(entry
->filename_size
) +
146 letoh16(entry
->extra_field_size
) +
147 letoh16(entry
->file_comment_size
);
153 struct cdir_end
*dirend
= (struct cdir_end
*)((char *) apk_file
+ apk_file_size
- sizeof(*dirend
));
154 uint32_t cdir_offset
;
156 while ((void *)dirend
> apk_file
&&
157 letoh32(dirend
->signature
) != CDIR_END_SIG
)
158 dirend
= (struct cdir_end
*)((char *)dirend
- 1);
159 if (letoh32(dirend
->signature
) != CDIR_END_SIG
) {
160 LOGE("setup_cdir: Could not find end of central directory record");
164 cdir_offset
= letoh32(dirend
->cdir_offset
);
166 cdir_entries
= letoh16(dirend
->cdir_entries
);
167 cdir_start
= (struct cdir_entry
*)((char *)apk_file
+ cdir_offset
);
172 static struct cdir_entry
*
173 find_cdir_entry(struct cdir_entry
*entry
, int count
, const char *name
)
175 size_t name_size
= strlen(name
);
177 if (letoh16(entry
->filename_size
) == name_size
&&
178 !memcmp(entry
->data
, name
, name_size
))
180 entry
= (struct cdir_entry
*)((char *)entry
+ cdir_entry_size(entry
));
186 handle_one_asset(struct cdir_entry
*entry
)
188 /* In the .apk there are no initial slashes */
189 const char *p
= entry
->data
+ sizeof("assets/")-1;
190 const char *z
= entry
->data
+ entry
->filename_size
;
191 direntry
*dir
= &assets
;
198 while (q
< z
&& *q
!= '/')
200 HASH_FIND(hh
, *dir
, p
, (unsigned)(q
- p
), old
);
203 new = malloc(sizeof(*new));
205 new->kind
= DIRECTORY
;
207 HASH_ADD_KEYPTR(hh
, *dir
, p
, (unsigned)(q
- p
), new);
215 new = malloc(sizeof(*new));
219 HASH_ADD_KEYPTR(hh
, *dir
, p
, (unsigned)(q
- p
), new);
221 LOGE("duplicate entry in apk: %.*s", entry
->filename_size
, entry
->data
);
230 setup_assets_tree(void)
232 int count
= cdir_entries
;
233 struct cdir_entry
*entry
= cdir_start
;
236 if (letoh16(entry
->filename_size
) >= sizeof("assets/")-1 &&
237 memcmp(entry
->data
, "assets/", sizeof("assets/")-1) == 0)
238 handle_one_asset(entry
);
239 entry
= (struct cdir_entry
*)((char *)entry
+ cdir_entry_size(entry
));
244 /* The lo-native-code shared library is always loaded from Java, so this is
245 * always called by JNI first.
247 __attribute__ ((visibility("default")))
249 JNI_OnLoad(JavaVM
* vm
, void* reserved
)
255 return JNI_VERSION_1_6
;
258 // public static native boolean setup(String dataDir,
262 __attribute__ ((visibility("default")))
264 Java_org_libreoffice_android_Bootstrap_setup__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2
273 const char *dataDirPath
;
274 const char *cacheDirPath
;
275 const char *apkFilePath
;
279 dataDirPath
= (*env
)->GetStringUTFChars(env
, dataDir
, NULL
);
280 data_dir
= strdup(dataDirPath
);
281 (*env
)->ReleaseStringUTFChars(env
, dataDir
, dataDirPath
);
283 cacheDirPath
= (*env
)->GetStringUTFChars(env
, cacheDir
, NULL
);
284 cache_dir
= strdup(cacheDirPath
);
285 (*env
)->ReleaseStringUTFChars(env
, cacheDir
, cacheDirPath
);
287 apkFilePath
= (*env
)->GetStringUTFChars(env
, apkFile
, NULL
);
289 fd
= open(apkFilePath
, O_RDONLY
);
291 LOGE("Could not open %s", apkFilePath
);
292 (*env
)->ReleaseStringUTFChars(env
, apkFile
, apkFilePath
);
295 if (fstat(fd
, &st
) == -1) {
296 LOGE("Could not fstat %s", apkFilePath
);
298 (*env
)->ReleaseStringUTFChars(env
, apkFile
, apkFilePath
);
301 apk_file
= mmap(NULL
, st
.st_size
, PROT_READ
, MAP_SHARED
, fd
, 0);
304 if (apk_file
== MAP_FAILED
) {
305 LOGE("Could not mmap %s", apkFilePath
);
306 (*env
)->ReleaseStringUTFChars(env
, apkFile
, apkFilePath
);
309 apk_file_size
= st
.st_size
;
311 (*env
)->ReleaseStringUTFChars(env
, apkFile
, apkFilePath
);
316 if (!setup_assets_tree())
323 get_jni_string_array(JNIEnv
*env
,
324 const char *function_and_parameter_name
,
332 StringArray
= (*env
)->FindClass(env
, "[Ljava/lang/String;");
333 if (StringArray
== NULL
) {
334 LOGE("Could not find String[] class");
338 if (!(*env
)->IsInstanceOf(env
, strv
, StringArray
)) {
339 LOGE("%s is not a String[]?", function_and_parameter_name
);
343 *argc
= (*env
)->GetArrayLength(env
, strv
);
344 *argv
= malloc(sizeof(char *) * (*argc
+1));
346 for (i
= 0; i
< *argc
; i
++) {
347 const char *s
= (*env
)->GetStringUTFChars(env
, (*env
)->GetObjectArrayElement(env
, strv
, i
), NULL
);
348 (*argv
)[i
] = strdup(s
);
349 (*env
)->ReleaseStringUTFChars(env
, (*env
)->GetObjectArrayElement(env
, strv
, i
), s
);
351 (*argv
)[*argc
] = NULL
;
356 // public static native int getpid();
358 __attribute__ ((visibility("default")))
360 Java_org_libreoffice_android_Bootstrap_getpid(JNIEnv
* env
,
369 // public static native void system(String cmdline);
371 __attribute__ ((visibility("default")))
373 Java_org_libreoffice_android_Bootstrap_system(JNIEnv
* env
,
381 s
= (*env
)->GetStringUTFChars(env
, cmdline
, NULL
);
383 LOGI("system(%s)", s
);
387 (*env
)->ReleaseStringUTFChars(env
, cmdline
, s
);
390 // public static native void putenv(String string);
392 __attribute__ ((visibility("default")))
394 Java_org_libreoffice_android_Bootstrap_putenv(JNIEnv
* env
,
403 s
= (*env
)->GetStringUTFChars(env
, string
, NULL
);
406 LOGI("putenv(%s)", s_copy
);
412 static int beenhere
=0;
414 LOGI("lo-bootstrap: Sleeping for 20 seconds, start ndk-gdb NOW if that is your intention");
421 (*env
)->ReleaseStringUTFChars(env
, string
, s
);
424 __attribute__ ((visibility("default")))
426 lo_apkentry(const char *filename
,
429 struct cdir_entry
*entry
;
430 struct local_file_header
*file
;
433 if (*filename
== '/')
436 entry
= find_cdir_entry(cdir_start
, cdir_entries
, filename
);
439 LOGE("lo_apkentry: Could not find %s", filename
);
442 file
= (struct local_file_header
*)((char *)apk_file
+ letoh32(entry
->offset
));
444 if (letoh16(file
->compression
) != STORE
) {
445 LOGE("lo_apkentry: File %s is compressed", filename
);
449 data
= ((char *)&file
->data
) + letoh16(file
->filename_size
) + letoh16(file
->extra_field_size
);
450 *size
= file
->uncompressed_size
;
452 /* LOGI("lo_apkentry(%s): %p, %d", filename, data, *size); */
457 __attribute__ ((visibility("default")))
459 lo_apk_opendir(const char *dirname
)
461 /* In the .apk there are no initial slashes, but the parameter passed to
464 const char *p
= dirname
+ sizeof("/assets/")-1;
465 direntry dir
= assets
;
468 lo_apk_dir
*result
= malloc(sizeof(*result
));
469 result
->cur
= assets
;
477 while (*q
&& *q
!= '/')
480 HASH_FIND(hh
, dir
, p
, (unsigned)(q
- p
), entry
);
482 if (entry
== NULL
&& *q
== '/') {
485 } else if (entry
== NULL
) {
486 /* Empty directories, or directories containing only "hidden"
487 * files (like the .gitignore in sc/qa/unit/qpro/indeterminate)
488 * are not present in the .apk. So we need to pretend that any
489 * directory that doesn't exist as a parent of an entry in the
490 * .apk *does* exist but is empty.
492 lo_apk_dir
*result
= malloc(sizeof(*result
));
497 if (entry
->kind
!= DIRECTORY
) {
502 if (!q
[0] || !q
[1]) {
503 lo_apk_dir
*result
= malloc(sizeof(*result
));
504 result
->cur
= entry
->subdir
;
513 __attribute__ ((visibility("default")))
515 lo_apk_readdir(lo_apk_dir
*dirp
)
517 static struct dirent result
;
519 if (dirp
->cur
== NULL
) {
520 /* LOGI("lo_apk_readdir(%p) = NULL", dirp); */
524 result
.d_ino
= dirp
->cur
->ino
;
528 if (dirp
->cur
->kind
== DIRECTORY
)
529 result
.d_type
= DT_DIR
;
531 result
.d_type
= DT_REG
;
533 memcpy(result
.d_name
, dirp
->cur
->hh
.key
, dirp
->cur
->hh
.keylen
);
534 result
.d_name
[dirp
->cur
->hh
.keylen
] = '\0';
536 dirp
->cur
= dirp
->cur
->hh
.next
;
538 /* LOGI("lo_apk_readdir(%p) = %s:%s", dirp, result.d_type == DT_DIR ? "DIR" : "REG", result.d_name); */
543 __attribute__ ((visibility("default")))
545 lo_apk_closedir(lo_apk_dir
*dirp
)
549 /* LOGI("lo_apk_closedir(%p)", dirp); */
555 new_stat(const char *path
,
557 struct cdir_entry
*entry
,
563 memset(statp
, 0, sizeof(*statp
));
564 statp
->st_mode
= mode
| S_IRUSR
;
567 statp
->st_uid
= getuid();
568 statp
->st_gid
= getgid();
571 statp
->st_size
= entry
->uncompressed_size
;
574 statp
->st_blksize
= 512;
575 if (statp
->st_size
== 0)
576 statp
->st_blocks
= 0;
578 statp
->st_blocks
= (statp
->st_size
- 1) / statp
->st_blksize
+ 1;
580 statp
->st_atime
= time(NULL
);
582 memset(&tm
, 0, sizeof(tm
));
583 tm
.tm_sec
= (letoh16(entry
->lastmod_time
) & 0x1F) * 2;
584 tm
.tm_min
= (letoh16(entry
->lastmod_time
) >> 5) & 0x3F;
585 tm
.tm_hour
= (letoh16(entry
->lastmod_time
) >> 11) & 0x1F;
586 tm
.tm_mday
= letoh16(entry
->lastmod_date
) & 0x1F;
587 tm
.tm_mon
= ((letoh16(entry
->lastmod_date
) >> 5) & 0x0F) - 1;
588 tm
.tm_year
= ((letoh16(entry
->lastmod_date
) >> 9) & 0x7F) + 80;
590 statp
->st_mtime
= mktime(&tm
);
591 statp
->st_ctime
= statp
->st_mtime
;
593 statp
->st_ino
= fake_ino
;
596 /* LOGI("lo_apk_lstat(%s) = { mode=%o, size=%lld, ino=%lld mtime=%.24s }",
597 path, statp->st_mode, statp->st_size, statp->st_ino,
598 ctime((const time_t *) &statp->st_mtime)); */
603 __attribute__ ((visibility("default")))
605 lo_apk_lstat(const char *path
,
608 const char *pn
= path
;
609 int count
= cdir_entries
;
610 struct cdir_entry
*entry
= cdir_start
;
616 return new_stat(path
, statp
, NULL
, S_IFDIR
| S_IXUSR
, 1);
619 name_size
= strlen(pn
);
622 if (letoh16(entry
->filename_size
) >= name_size
&&
623 !memcmp(entry
->data
, pn
, name_size
) &&
624 (letoh16(entry
->filename_size
) == name_size
|| entry
->data
[name_size
] == '/'))
626 entry
= (struct cdir_entry
*)((char *)entry
+ cdir_entry_size(entry
));
629 if (letoh16(entry
->filename_size
) == name_size
)
630 return new_stat(path
, statp
, entry
, S_IFREG
, cdir_entries
- count
+ 1);
632 return new_stat(path
, statp
, entry
, S_IFDIR
| S_IXUSR
, cdir_entries
- count
+ 1);
639 /* Android's JNI works only to libraries loaded through Java's
640 * System.loadLibrary(), it seems. But now with just one big app-specific .so
641 * on Android, that would not be a problem, but for historical reasons, we
642 * have JNI wrappers here, and then call the VCL etc function from them. Oh
643 * well, one could say it's clean to have all the Android-specific JNI
644 * functions here in this file.
647 extern void osl_setCommandArgs(int, char **);
649 __attribute__ ((visibility("default")))
651 Java_org_libreoffice_android_Bootstrap_setCommandArgs(JNIEnv
* env
,
657 Dl_info lo_bootstrap_info
;
661 if (!get_jni_string_array(env
, "setCommandArgs :argv", argv
, &c_argc
, (const char ***) &c_argv
))
664 if (dladdr(Java_org_libreoffice_android_Bootstrap_setCommandArgs
, &lo_bootstrap_info
) != 0) {
665 char *new_argv0
= malloc(strlen(lo_bootstrap_info
.dli_fname
) + strlen(c_argv
[0]));
667 strcpy(new_argv0
, lo_bootstrap_info
.dli_fname
);
668 slash
= strrchr(new_argv0
, '/');
671 slash
= strrchr(new_argv0
, '/');
673 strcpy(slash
+1, c_argv
[0]);
675 strcpy(new_argv0
, c_argv
[0]);
677 c_argv
[0] = new_argv0
;
680 osl_setCommandArgs(c_argc
, c_argv
);
683 /* Code for reading lines from the pipe based on the (Apache-licensed) Android
688 read_from(int fd
, const char *tag
, char *buffer
, int *sz
, int *a
, int *b
, size_t sizeof_buffer
)
692 nread
= read(fd
, buffer
+*b
, sizeof_buffer
- 1 - *b
);
696 LOGE("redirect_thread: Reading from %d failed: %s", fd
, strerror(errno
));
702 LOGI("redirect_thread: EOF from fd %d", fd
);
709 for (*b
= 0; *b
< *sz
; (*b
)++) {
710 if (buffer
[*b
] == '\n') {
712 __android_log_print(ANDROID_LOG_INFO
, tag
, "%s", &buffer
[*a
]);
717 if (*a
== 0 && *b
== (int) sizeof_buffer
- 1) {
718 // buffer is full, flush
720 __android_log_print(ANDROID_LOG_INFO
, tag
, "%s", &buffer
[*a
]);
722 } else if (*a
!= *b
) {
725 memmove(buffer
, &buffer
[*a
], *b
);
735 static int stdout_pipe
[2], stderr_pipe
[2];
738 redirect_thread(void *arg
)
740 char buffer
[2][4096];
752 if (stdout_pipe
[0] != -1) {
753 FD_SET(stdout_pipe
[0], &readfds
);
754 nfds
= MAX(nfds
, stdout_pipe
[0] + 1);
756 if (stderr_pipe
[0] != -1) {
757 FD_SET(stderr_pipe
[0], &readfds
);
758 nfds
= MAX(nfds
, stderr_pipe
[0] + 1);
761 LOGI("redirect_thread: Nothing to read any more, thread exiting");
765 if (select(nfds
, &readfds
, NULL
, NULL
, NULL
) == -1) {
766 LOGE("redirect_thread: select failed: %s, thread exiting", strerror(errno
));
767 close(stdout_pipe
[0]);
769 close(stderr_pipe
[0]);
774 if (stdout_pipe
[0] != -1 &&
775 FD_ISSET(stdout_pipe
[0], &readfds
)) {
776 if (read_from(stdout_pipe
[0], "stdout", buffer
[0], &sz
[0], &a
[0], &b
[0], sizeof(buffer
[0])) <= 0) {
781 if (stderr_pipe
[0] != -1 &&
782 FD_ISSET(stderr_pipe
[0], &readfds
)) {
783 if (read_from(stderr_pipe
[0], "stderr", buffer
[1], &sz
[1], &a
[1], &b
[1], sizeof(buffer
[1])) <= 0) {
791 redirect_to_null(void)
793 int null
= open("/dev/null", O_WRONLY
);
795 LOGE("redirect_stdio: Could not open /dev/null: %s", strerror(errno
));
796 /* If we can't redirect stdout or stderr to /dev/null, just close them
803 if (dup2(null
, 1) == -1) {
804 LOGE("redirect_stdio: Could not dup2 %d to 1: %s", null
, strerror(errno
));
810 if (dup2(null
, 2) == -1) {
811 LOGE("redirect_stdio: Could not dup2 %d to 2: %s", null
, strerror(errno
));
821 __attribute__ ((visibility("default")))
823 Java_org_libreoffice_android_Bootstrap_redirect_1stdio(JNIEnv
* env
,
827 static jboolean current
= JNI_FALSE
;
834 if (state
== current
)
837 if (state
== JNI_FALSE
) {
838 if (!redirect_to_null())
841 if (pipe(stdout_pipe
) == -1) {
842 LOGE("redirect_stdio: Could not create pipes: %s", strerror(errno
));
845 if (pipe(stderr_pipe
) == -1) {
846 LOGE("redirect_stdio: Could not create pipes: %s", strerror(errno
));
847 close(stdout_pipe
[0]);
848 close(stdout_pipe
[1]);
851 LOGI("redirect_stdio: stdout pipe: [%d,%d], stderr pipe: [%d,%d]",
852 stdout_pipe
[0], stdout_pipe
[1], stderr_pipe
[0], stderr_pipe
[1]);
854 if (dup2(stdout_pipe
[1], 1) == -1) {
855 LOGE("redirect_stdio: Could not dup2 %d to 1: %s", stdout_pipe
[1], strerror(errno
));
856 close(stdout_pipe
[0]);
857 close(stdout_pipe
[1]);
858 close(stderr_pipe
[0]);
859 close(stderr_pipe
[1]);
863 if (dup2(stderr_pipe
[1], 2) == -1) {
864 LOGE("redirect_stdio: Could not dup2 %d to 2: %s", stdout_pipe
[1], strerror(errno
));
865 /* stdout has already been redirected to its pipe, so redirect
866 * it back to /dev/null
869 close(stdout_pipe
[0]);
870 close(stdout_pipe
[1]);
871 close(stderr_pipe
[0]);
872 close(stderr_pipe
[1]);
875 close(stdout_pipe
[1]);
876 close(stderr_pipe
[1]);
878 if (pthread_attr_init(&attr
) != 0 ||
879 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
) != 0 ||
880 pthread_create(&thread
, &attr
, redirect_thread
, NULL
) != 0) {
881 LOGE("redirect_stdio: Could not create thread: %s", strerror(errno
));
883 close(stdout_pipe
[0]);
884 close(stderr_pipe
[0]);
892 __attribute__ ((visibility("default")))
894 Java_org_libreoffice_android_Bootstrap_address_1of_1direct_1byte_1buffer(JNIEnv
*env
,
897 return (jlong
) (intptr_t) (*env
)->GetDirectBufferAddress(env
, bbuffer
);
900 __attribute__ ((visibility("default")))
902 libreofficekit_set_javavm(JavaVM
*vm
)
907 __attribute__ ((visibility("default")))
914 __attribute__ ((visibility("default")))
916 lo_get_app_data_dir(void)
921 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */