8020675: invalid jar file in the bootclasspath could lead to jvm fatal error
Removed offending EXCEPTION_MARK calls and code cleanup Reviewed-by: dholmes, iklam, coleenp, mseledtsov
This commit is contained in:
parent
15464d7992
commit
4415ae47cd
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -197,7 +197,7 @@ ClassPathDirEntry::ClassPathDirEntry(char* dir) : ClassPathEntry() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ClassFileStream* ClassPathDirEntry::open_stream(const char* name) {
|
ClassFileStream* ClassPathDirEntry::open_stream(const char* name, TRAPS) {
|
||||||
// construct full path name
|
// construct full path name
|
||||||
char path[JVM_MAXPATHLEN];
|
char path[JVM_MAXPATHLEN];
|
||||||
if (jio_snprintf(path, sizeof(path), "%s%s%s", _dir, os::file_separator(), name) == -1) {
|
if (jio_snprintf(path, sizeof(path), "%s%s%s", _dir, os::file_separator(), name) == -1) {
|
||||||
@ -240,7 +240,7 @@ ClassPathZipEntry::~ClassPathZipEntry() {
|
|||||||
FREE_C_HEAP_ARRAY(char, _zip_name, mtClass);
|
FREE_C_HEAP_ARRAY(char, _zip_name, mtClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassFileStream* ClassPathZipEntry::open_stream(const char* name) {
|
ClassFileStream* ClassPathZipEntry::open_stream(const char* name, TRAPS) {
|
||||||
// enable call to C land
|
// enable call to C land
|
||||||
JavaThread* thread = JavaThread::current();
|
JavaThread* thread = JavaThread::current();
|
||||||
ThreadToNativeFromVM ttn(thread);
|
ThreadToNativeFromVM ttn(thread);
|
||||||
@ -284,24 +284,24 @@ void ClassPathZipEntry::contents_do(void f(const char* name, void* context), voi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyClassPathEntry::LazyClassPathEntry(char* path, struct stat st) : ClassPathEntry() {
|
LazyClassPathEntry::LazyClassPathEntry(char* path, const struct stat* st) : ClassPathEntry() {
|
||||||
_path = strdup(path);
|
_path = strdup(path);
|
||||||
_st = st;
|
_st = *st;
|
||||||
_meta_index = NULL;
|
_meta_index = NULL;
|
||||||
_resolved_entry = NULL;
|
_resolved_entry = NULL;
|
||||||
|
_has_error = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LazyClassPathEntry::is_jar_file() {
|
bool LazyClassPathEntry::is_jar_file() {
|
||||||
return ((_st.st_mode & S_IFREG) == S_IFREG);
|
return ((_st.st_mode & S_IFREG) == S_IFREG);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassPathEntry* LazyClassPathEntry::resolve_entry() {
|
ClassPathEntry* LazyClassPathEntry::resolve_entry(TRAPS) {
|
||||||
if (_resolved_entry != NULL) {
|
if (_resolved_entry != NULL) {
|
||||||
return (ClassPathEntry*) _resolved_entry;
|
return (ClassPathEntry*) _resolved_entry;
|
||||||
}
|
}
|
||||||
ClassPathEntry* new_entry = NULL;
|
ClassPathEntry* new_entry = NULL;
|
||||||
ClassLoader::create_class_path_entry(_path, _st, &new_entry, false);
|
new_entry = ClassLoader::create_class_path_entry(_path, &_st, false, CHECK_NULL);
|
||||||
assert(new_entry != NULL, "earlier code should have caught this");
|
|
||||||
{
|
{
|
||||||
ThreadCritical tc;
|
ThreadCritical tc;
|
||||||
if (_resolved_entry == NULL) {
|
if (_resolved_entry == NULL) {
|
||||||
@ -314,12 +314,21 @@ ClassPathEntry* LazyClassPathEntry::resolve_entry() {
|
|||||||
return (ClassPathEntry*) _resolved_entry;
|
return (ClassPathEntry*) _resolved_entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassFileStream* LazyClassPathEntry::open_stream(const char* name) {
|
ClassFileStream* LazyClassPathEntry::open_stream(const char* name, TRAPS) {
|
||||||
if (_meta_index != NULL &&
|
if (_meta_index != NULL &&
|
||||||
!_meta_index->may_contain(name)) {
|
!_meta_index->may_contain(name)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return resolve_entry()->open_stream(name);
|
if (_has_error) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ClassPathEntry* cpe = resolve_entry(THREAD);
|
||||||
|
if (cpe == NULL) {
|
||||||
|
_has_error = true;
|
||||||
|
return NULL;
|
||||||
|
} else {
|
||||||
|
return cpe->open_stream(name, THREAD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LazyClassPathEntry::is_lazy() {
|
bool LazyClassPathEntry::is_lazy() {
|
||||||
@ -465,20 +474,19 @@ void ClassLoader::setup_bootstrap_search_path() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClassLoader::create_class_path_entry(char *path, struct stat st, ClassPathEntry **new_entry, bool lazy) {
|
ClassPathEntry* ClassLoader::create_class_path_entry(char *path, const struct stat* st, bool lazy, TRAPS) {
|
||||||
JavaThread* thread = JavaThread::current();
|
JavaThread* thread = JavaThread::current();
|
||||||
if (lazy) {
|
if (lazy) {
|
||||||
*new_entry = new LazyClassPathEntry(path, st);
|
return new LazyClassPathEntry(path, st);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if ((st.st_mode & S_IFREG) == S_IFREG) {
|
ClassPathEntry* new_entry = NULL;
|
||||||
|
if ((st->st_mode & S_IFREG) == S_IFREG) {
|
||||||
// Regular file, should be a zip file
|
// Regular file, should be a zip file
|
||||||
// Canonicalized filename
|
// Canonicalized filename
|
||||||
char canonical_path[JVM_MAXPATHLEN];
|
char canonical_path[JVM_MAXPATHLEN];
|
||||||
if (!get_canonical_path(path, canonical_path, JVM_MAXPATHLEN)) {
|
if (!get_canonical_path(path, canonical_path, JVM_MAXPATHLEN)) {
|
||||||
// This matches the classic VM
|
// This matches the classic VM
|
||||||
EXCEPTION_MARK;
|
THROW_MSG_(vmSymbols::java_io_IOException(), "Bad pathname", NULL);
|
||||||
THROW_MSG(vmSymbols::java_io_IOException(), "Bad pathname");
|
|
||||||
}
|
}
|
||||||
char* error_msg = NULL;
|
char* error_msg = NULL;
|
||||||
jzfile* zip;
|
jzfile* zip;
|
||||||
@ -489,7 +497,7 @@ void ClassLoader::create_class_path_entry(char *path, struct stat st, ClassPathE
|
|||||||
zip = (*ZipOpen)(canonical_path, &error_msg);
|
zip = (*ZipOpen)(canonical_path, &error_msg);
|
||||||
}
|
}
|
||||||
if (zip != NULL && error_msg == NULL) {
|
if (zip != NULL && error_msg == NULL) {
|
||||||
*new_entry = new ClassPathZipEntry(zip, path);
|
new_entry = new ClassPathZipEntry(zip, path);
|
||||||
if (TraceClassLoading) {
|
if (TraceClassLoading) {
|
||||||
tty->print_cr("[Opened %s]", path);
|
tty->print_cr("[Opened %s]", path);
|
||||||
}
|
}
|
||||||
@ -504,16 +512,16 @@ void ClassLoader::create_class_path_entry(char *path, struct stat st, ClassPathE
|
|||||||
msg = NEW_RESOURCE_ARRAY(char, len); ;
|
msg = NEW_RESOURCE_ARRAY(char, len); ;
|
||||||
jio_snprintf(msg, len - 1, "error in opening JAR file <%s> %s", error_msg, path);
|
jio_snprintf(msg, len - 1, "error in opening JAR file <%s> %s", error_msg, path);
|
||||||
}
|
}
|
||||||
EXCEPTION_MARK;
|
THROW_MSG_(vmSymbols::java_lang_ClassNotFoundException(), msg, NULL);
|
||||||
THROW_MSG(vmSymbols::java_lang_ClassNotFoundException(), msg);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Directory
|
// Directory
|
||||||
*new_entry = new ClassPathDirEntry(path);
|
new_entry = new ClassPathDirEntry(path);
|
||||||
if (TraceClassLoading) {
|
if (TraceClassLoading) {
|
||||||
tty->print_cr("[Path %s]", path);
|
tty->print_cr("[Path %s]", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return new_entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -572,13 +580,14 @@ void ClassLoader::add_to_list(ClassPathEntry *new_entry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClassLoader::update_class_path_entry_list(const char *path,
|
void ClassLoader::update_class_path_entry_list(char *path,
|
||||||
bool check_for_duplicates) {
|
bool check_for_duplicates) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (os::stat((char *)path, &st) == 0) {
|
if (os::stat(path, &st) == 0) {
|
||||||
// File or directory found
|
// File or directory found
|
||||||
ClassPathEntry* new_entry = NULL;
|
ClassPathEntry* new_entry = NULL;
|
||||||
create_class_path_entry((char *)path, st, &new_entry, LazyBootClassLoader);
|
Thread* THREAD = Thread::current();
|
||||||
|
new_entry = create_class_path_entry(path, &st, LazyBootClassLoader, CHECK);
|
||||||
// The kernel VM adds dynamically to the end of the classloader path and
|
// The kernel VM adds dynamically to the end of the classloader path and
|
||||||
// doesn't reorder the bootclasspath which would break java.lang.Package
|
// doesn't reorder the bootclasspath which would break java.lang.Package
|
||||||
// (see PackageInfo).
|
// (see PackageInfo).
|
||||||
@ -897,7 +906,7 @@ instanceKlassHandle ClassLoader::load_classfile(Symbol* h_name, TRAPS) {
|
|||||||
PerfClassTraceTime::CLASS_LOAD);
|
PerfClassTraceTime::CLASS_LOAD);
|
||||||
ClassPathEntry* e = _first_entry;
|
ClassPathEntry* e = _first_entry;
|
||||||
while (e != NULL) {
|
while (e != NULL) {
|
||||||
stream = e->open_stream(name);
|
stream = e->open_stream(name, CHECK_NULL);
|
||||||
if (stream != NULL) {
|
if (stream != NULL) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1257,11 +1266,16 @@ bool ClassPathZipEntry::is_rt_jar12() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyClassPathEntry::compile_the_world(Handle loader, TRAPS) {
|
void LazyClassPathEntry::compile_the_world(Handle loader, TRAPS) {
|
||||||
resolve_entry()->compile_the_world(loader, CHECK);
|
ClassPathEntry* cpe = resolve_entry(THREAD);
|
||||||
|
if (cpe != NULL) {
|
||||||
|
cpe->compile_the_world(loader, CHECK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LazyClassPathEntry::is_rt_jar() {
|
bool LazyClassPathEntry::is_rt_jar() {
|
||||||
return resolve_entry()->is_rt_jar();
|
Thread* THREAD = Thread::current();
|
||||||
|
ClassPathEntry* cpe = resolve_entry(THREAD);
|
||||||
|
return (cpe != NULL) ? cpe->is_jar_file() : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClassLoader::compile_the_world() {
|
void ClassLoader::compile_the_world() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -63,7 +63,7 @@ class ClassPathEntry: public CHeapObj<mtClass> {
|
|||||||
ClassPathEntry();
|
ClassPathEntry();
|
||||||
// Attempt to locate file_name through this class path entry.
|
// Attempt to locate file_name through this class path entry.
|
||||||
// Returns a class file parsing stream if successfull.
|
// Returns a class file parsing stream if successfull.
|
||||||
virtual ClassFileStream* open_stream(const char* name) = 0;
|
virtual ClassFileStream* open_stream(const char* name, TRAPS) = 0;
|
||||||
// Debugging
|
// Debugging
|
||||||
NOT_PRODUCT(virtual void compile_the_world(Handle loader, TRAPS) = 0;)
|
NOT_PRODUCT(virtual void compile_the_world(Handle loader, TRAPS) = 0;)
|
||||||
NOT_PRODUCT(virtual bool is_rt_jar() = 0;)
|
NOT_PRODUCT(virtual bool is_rt_jar() = 0;)
|
||||||
@ -77,7 +77,7 @@ class ClassPathDirEntry: public ClassPathEntry {
|
|||||||
bool is_jar_file() { return false; }
|
bool is_jar_file() { return false; }
|
||||||
const char* name() { return _dir; }
|
const char* name() { return _dir; }
|
||||||
ClassPathDirEntry(char* dir);
|
ClassPathDirEntry(char* dir);
|
||||||
ClassFileStream* open_stream(const char* name);
|
ClassFileStream* open_stream(const char* name, TRAPS);
|
||||||
// Debugging
|
// Debugging
|
||||||
NOT_PRODUCT(void compile_the_world(Handle loader, TRAPS);)
|
NOT_PRODUCT(void compile_the_world(Handle loader, TRAPS);)
|
||||||
NOT_PRODUCT(bool is_rt_jar();)
|
NOT_PRODUCT(bool is_rt_jar();)
|
||||||
@ -107,7 +107,7 @@ class ClassPathZipEntry: public ClassPathEntry {
|
|||||||
const char* name() { return _zip_name; }
|
const char* name() { return _zip_name; }
|
||||||
ClassPathZipEntry(jzfile* zip, const char* zip_name);
|
ClassPathZipEntry(jzfile* zip, const char* zip_name);
|
||||||
~ClassPathZipEntry();
|
~ClassPathZipEntry();
|
||||||
ClassFileStream* open_stream(const char* name);
|
ClassFileStream* open_stream(const char* name, TRAPS);
|
||||||
void contents_do(void f(const char* name, void* context), void* context);
|
void contents_do(void f(const char* name, void* context), void* context);
|
||||||
// Debugging
|
// Debugging
|
||||||
NOT_PRODUCT(void compile_the_world(Handle loader, TRAPS);)
|
NOT_PRODUCT(void compile_the_world(Handle loader, TRAPS);)
|
||||||
@ -125,13 +125,14 @@ class LazyClassPathEntry: public ClassPathEntry {
|
|||||||
char* _path; // dir or file
|
char* _path; // dir or file
|
||||||
struct stat _st;
|
struct stat _st;
|
||||||
MetaIndex* _meta_index;
|
MetaIndex* _meta_index;
|
||||||
|
bool _has_error;
|
||||||
volatile ClassPathEntry* _resolved_entry;
|
volatile ClassPathEntry* _resolved_entry;
|
||||||
ClassPathEntry* resolve_entry();
|
ClassPathEntry* resolve_entry(TRAPS);
|
||||||
public:
|
public:
|
||||||
bool is_jar_file();
|
bool is_jar_file();
|
||||||
const char* name() { return _path; }
|
const char* name() { return _path; }
|
||||||
LazyClassPathEntry(char* path, struct stat st);
|
LazyClassPathEntry(char* path, const struct stat* st);
|
||||||
ClassFileStream* open_stream(const char* name);
|
ClassFileStream* open_stream(const char* name, TRAPS);
|
||||||
void set_meta_index(MetaIndex* meta_index) { _meta_index = meta_index; }
|
void set_meta_index(MetaIndex* meta_index) { _meta_index = meta_index; }
|
||||||
virtual bool is_lazy();
|
virtual bool is_lazy();
|
||||||
// Debugging
|
// Debugging
|
||||||
@ -207,14 +208,15 @@ class ClassLoader: AllStatic {
|
|||||||
static void setup_meta_index();
|
static void setup_meta_index();
|
||||||
static void setup_bootstrap_search_path();
|
static void setup_bootstrap_search_path();
|
||||||
static void load_zip_library();
|
static void load_zip_library();
|
||||||
static void create_class_path_entry(char *path, struct stat st, ClassPathEntry **new_entry, bool lazy);
|
static ClassPathEntry* create_class_path_entry(char *path, const struct stat* st,
|
||||||
|
bool lazy, TRAPS);
|
||||||
|
|
||||||
// Canonicalizes path names, so strcmp will work properly. This is mainly
|
// Canonicalizes path names, so strcmp will work properly. This is mainly
|
||||||
// to avoid confusing the zip library
|
// to avoid confusing the zip library
|
||||||
static bool get_canonical_path(char* orig, char* out, int len);
|
static bool get_canonical_path(char* orig, char* out, int len);
|
||||||
public:
|
public:
|
||||||
// Used by the kernel jvm.
|
// Used by the kernel jvm.
|
||||||
static void update_class_path_entry_list(const char *path,
|
static void update_class_path_entry_list(char *path,
|
||||||
bool check_for_duplicates);
|
bool check_for_duplicates);
|
||||||
static void print_bootclasspath();
|
static void print_bootclasspath();
|
||||||
|
|
||||||
|
51
hotspot/test/runtime/LoadClass/LoadClassNegative.java
Normal file
51
hotspot/test/runtime/LoadClass/LoadClassNegative.java
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code 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
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @key regression
|
||||||
|
* @bug 8020675
|
||||||
|
* @summary make sure there is no fatal error if a class is loaded from an invalid jar file which is in the bootclasspath
|
||||||
|
* @library /testlibrary
|
||||||
|
* @build TestForName
|
||||||
|
* @build LoadClassNegative
|
||||||
|
* @run main LoadClassNegative
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import com.oracle.java.testlibrary.*;
|
||||||
|
|
||||||
|
public class LoadClassNegative {
|
||||||
|
|
||||||
|
public static void main(String args[]) throws Exception {
|
||||||
|
String bootCP = "-Xbootclasspath/a:" + System.getProperty("test.src")
|
||||||
|
+ File.separator + "dummy.jar";
|
||||||
|
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
|
||||||
|
bootCP,
|
||||||
|
"TestForName");
|
||||||
|
|
||||||
|
OutputAnalyzer output = new OutputAnalyzer(pb.start());
|
||||||
|
output.shouldContain("ClassNotFoundException");
|
||||||
|
output.shouldHaveExitValue(0);
|
||||||
|
}
|
||||||
|
}
|
33
hotspot/test/runtime/LoadClass/TestForName.java
Normal file
33
hotspot/test/runtime/LoadClass/TestForName.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code 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
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class TestForName {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
Class cls = Class.forName("xxx");
|
||||||
|
System.out.println("Class = " + cls.getName());
|
||||||
|
} catch (ClassNotFoundException cnfe) {
|
||||||
|
cnfe.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
0
hotspot/test/runtime/LoadClass/dummy.jar
Normal file
0
hotspot/test/runtime/LoadClass/dummy.jar
Normal file
Loading…
x
Reference in New Issue
Block a user