|
|
|
@@ -296,8 +296,8 @@ static int console_msg_format = MSG_FORMAT_DEFAULT;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The printk log buffer consists of a sequenced collection of records, each
|
|
|
|
|
* containing variable length message and dictionary text. Every record
|
|
|
|
|
* also contains its own meta-data (@info).
|
|
|
|
|
* containing variable length message text. Every record also contains its
|
|
|
|
|
* own meta-data (@info).
|
|
|
|
|
*
|
|
|
|
|
* Every record meta-data carries the timestamp in microseconds, as well as
|
|
|
|
|
* the standard userspace syslog level and syslog facility. The usual kernel
|
|
|
|
@@ -310,9 +310,7 @@ static int console_msg_format = MSG_FORMAT_DEFAULT;
|
|
|
|
|
* terminated.
|
|
|
|
|
*
|
|
|
|
|
* Optionally, a record can carry a dictionary of properties (key/value
|
|
|
|
|
* pairs), to provide userspace with a machine-readable message context. The
|
|
|
|
|
* length of the dictionary is available in @dict_len. The dictionary is not
|
|
|
|
|
* terminated.
|
|
|
|
|
* pairs), to provide userspace with a machine-readable message context.
|
|
|
|
|
*
|
|
|
|
|
* Examples for well-defined, commonly used property names are:
|
|
|
|
|
* DEVICE=b12:8 device identifier
|
|
|
|
@@ -322,21 +320,20 @@ static int console_msg_format = MSG_FORMAT_DEFAULT;
|
|
|
|
|
* +sound:card0 subsystem:devname
|
|
|
|
|
* SUBSYSTEM=pci driver-core subsystem name
|
|
|
|
|
*
|
|
|
|
|
* Valid characters in property names are [a-zA-Z0-9.-_]. The plain text value
|
|
|
|
|
* follows directly after a '=' character. Every property is terminated by
|
|
|
|
|
* a '\0' character. The last property is not terminated.
|
|
|
|
|
* Valid characters in property names are [a-zA-Z0-9.-_]. Property names
|
|
|
|
|
* and values are terminated by a '\0' character.
|
|
|
|
|
*
|
|
|
|
|
* Example of record values:
|
|
|
|
|
* record.text_buf = "it's a line" (unterminated)
|
|
|
|
|
* record.dict_buf = "DEVICE=b8:2\0DRIVER=bug" (unterminated)
|
|
|
|
|
* record.info.seq = 56
|
|
|
|
|
* record.info.ts_nsec = 36863
|
|
|
|
|
* record.info.text_len = 11
|
|
|
|
|
* record.info.dict_len = 22
|
|
|
|
|
* record.info.facility = 0 (LOG_KERN)
|
|
|
|
|
* record.info.flags = 0
|
|
|
|
|
* record.info.level = 3 (LOG_ERR)
|
|
|
|
|
* record.info.caller_id = 299 (task 299)
|
|
|
|
|
* record.text_buf = "it's a line" (unterminated)
|
|
|
|
|
* record.info.seq = 56
|
|
|
|
|
* record.info.ts_nsec = 36863
|
|
|
|
|
* record.info.text_len = 11
|
|
|
|
|
* record.info.facility = 0 (LOG_KERN)
|
|
|
|
|
* record.info.flags = 0
|
|
|
|
|
* record.info.level = 3 (LOG_ERR)
|
|
|
|
|
* record.info.caller_id = 299 (task 299)
|
|
|
|
|
* record.info.dev_info.subsystem = "pci" (terminated)
|
|
|
|
|
* record.info.dev_info.device = "+pci:0000:00:01.0" (terminated)
|
|
|
|
|
*
|
|
|
|
|
* The 'struct printk_info' buffer must never be directly exported to
|
|
|
|
|
* userspace, it is a kernel-private implementation detail that might
|
|
|
|
@@ -498,19 +495,19 @@ static void truncate_msg(u16 *text_len, u16 *trunc_msg_len)
|
|
|
|
|
/* insert record into the buffer, discard old ones, update heads */
|
|
|
|
|
static int log_store(u32 caller_id, int facility, int level,
|
|
|
|
|
enum log_flags flags, u64 ts_nsec,
|
|
|
|
|
const char *dict, u16 dict_len,
|
|
|
|
|
const struct dev_printk_info *dev_info,
|
|
|
|
|
const char *text, u16 text_len)
|
|
|
|
|
{
|
|
|
|
|
struct prb_reserved_entry e;
|
|
|
|
|
struct printk_record r;
|
|
|
|
|
u16 trunc_msg_len = 0;
|
|
|
|
|
|
|
|
|
|
prb_rec_init_wr(&r, text_len, dict_len);
|
|
|
|
|
prb_rec_init_wr(&r, text_len, 0);
|
|
|
|
|
|
|
|
|
|
if (!prb_reserve(&e, prb, &r)) {
|
|
|
|
|
/* truncate the message if it is too long for empty buffer */
|
|
|
|
|
truncate_msg(&text_len, &trunc_msg_len);
|
|
|
|
|
prb_rec_init_wr(&r, text_len + trunc_msg_len, dict_len);
|
|
|
|
|
prb_rec_init_wr(&r, text_len + trunc_msg_len, 0);
|
|
|
|
|
/* survive when the log buffer is too small for trunc_msg */
|
|
|
|
|
if (!prb_reserve(&e, prb, &r))
|
|
|
|
|
return 0;
|
|
|
|
@@ -521,10 +518,6 @@ static int log_store(u32 caller_id, int facility, int level,
|
|
|
|
|
if (trunc_msg_len)
|
|
|
|
|
memcpy(&r.text_buf[text_len], trunc_msg, trunc_msg_len);
|
|
|
|
|
r.info->text_len = text_len + trunc_msg_len;
|
|
|
|
|
if (r.dict_buf) {
|
|
|
|
|
memcpy(&r.dict_buf[0], dict, dict_len);
|
|
|
|
|
r.info->dict_len = dict_len;
|
|
|
|
|
}
|
|
|
|
|
r.info->facility = facility;
|
|
|
|
|
r.info->level = level & 7;
|
|
|
|
|
r.info->flags = flags & 0x1f;
|
|
|
|
@@ -533,6 +526,8 @@ static int log_store(u32 caller_id, int facility, int level,
|
|
|
|
|
else
|
|
|
|
|
r.info->ts_nsec = local_clock();
|
|
|
|
|
r.info->caller_id = caller_id;
|
|
|
|
|
if (dev_info)
|
|
|
|
|
memcpy(&r.info->dev_info, dev_info, sizeof(r.info->dev_info));
|
|
|
|
|
|
|
|
|
|
/* insert message */
|
|
|
|
|
if ((flags & LOG_CONT) || !(flags & LOG_NEWLINE))
|
|
|
|
@@ -613,9 +608,9 @@ static ssize_t info_print_ext_header(char *buf, size_t size,
|
|
|
|
|
ts_usec, info->flags & LOG_CONT ? 'c' : '-', caller);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t msg_print_ext_body(char *buf, size_t size,
|
|
|
|
|
char *dict, size_t dict_len,
|
|
|
|
|
char *text, size_t text_len)
|
|
|
|
|
static ssize_t msg_add_ext_text(char *buf, size_t size,
|
|
|
|
|
const char *text, size_t text_len,
|
|
|
|
|
unsigned char endc)
|
|
|
|
|
{
|
|
|
|
|
char *p = buf, *e = buf + size;
|
|
|
|
|
size_t i;
|
|
|
|
@@ -629,38 +624,46 @@ static ssize_t msg_print_ext_body(char *buf, size_t size,
|
|
|
|
|
else
|
|
|
|
|
append_char(&p, e, c);
|
|
|
|
|
}
|
|
|
|
|
append_char(&p, e, '\n');
|
|
|
|
|
|
|
|
|
|
if (dict_len) {
|
|
|
|
|
bool line = true;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < dict_len; i++) {
|
|
|
|
|
unsigned char c = dict[i];
|
|
|
|
|
|
|
|
|
|
if (line) {
|
|
|
|
|
append_char(&p, e, ' ');
|
|
|
|
|
line = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (c == '\0') {
|
|
|
|
|
append_char(&p, e, '\n');
|
|
|
|
|
line = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (c < ' ' || c >= 127 || c == '\\') {
|
|
|
|
|
p += scnprintf(p, e - p, "\\x%02x", c);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
append_char(&p, e, c);
|
|
|
|
|
}
|
|
|
|
|
append_char(&p, e, '\n');
|
|
|
|
|
}
|
|
|
|
|
append_char(&p, e, endc);
|
|
|
|
|
|
|
|
|
|
return p - buf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t msg_add_dict_text(char *buf, size_t size,
|
|
|
|
|
const char *key, const char *val)
|
|
|
|
|
{
|
|
|
|
|
size_t val_len = strlen(val);
|
|
|
|
|
ssize_t len;
|
|
|
|
|
|
|
|
|
|
if (!val_len)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
len = msg_add_ext_text(buf, size, "", 0, ' '); /* dict prefix */
|
|
|
|
|
len += msg_add_ext_text(buf + len, size - len, key, strlen(key), '=');
|
|
|
|
|
len += msg_add_ext_text(buf + len, size - len, val, val_len, '\n');
|
|
|
|
|
|
|
|
|
|
return len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t msg_print_ext_body(char *buf, size_t size,
|
|
|
|
|
char *text, size_t text_len,
|
|
|
|
|
struct dev_printk_info *dev_info)
|
|
|
|
|
{
|
|
|
|
|
ssize_t len;
|
|
|
|
|
|
|
|
|
|
len = msg_add_ext_text(buf, size, text, text_len, '\n');
|
|
|
|
|
|
|
|
|
|
if (!dev_info)
|
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
|
|
len += msg_add_dict_text(buf + len, size - len, "SUBSYSTEM",
|
|
|
|
|
dev_info->subsystem);
|
|
|
|
|
len += msg_add_dict_text(buf + len, size - len, "DEVICE",
|
|
|
|
|
dev_info->device);
|
|
|
|
|
out:
|
|
|
|
|
return len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* /dev/kmsg - userspace message inject/listen interface */
|
|
|
|
|
struct devkmsg_user {
|
|
|
|
|
u64 seq;
|
|
|
|
@@ -670,7 +673,6 @@ struct devkmsg_user {
|
|
|
|
|
|
|
|
|
|
struct printk_info info;
|
|
|
|
|
char text_buf[CONSOLE_EXT_LOG_MAX];
|
|
|
|
|
char dict_buf[CONSOLE_EXT_LOG_MAX];
|
|
|
|
|
struct printk_record record;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@@ -681,7 +683,7 @@ int devkmsg_emit(int facility, int level, const char *fmt, ...)
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
va_start(args, fmt);
|
|
|
|
|
r = vprintk_emit(facility, level, NULL, 0, fmt, args);
|
|
|
|
|
r = vprintk_emit(facility, level, NULL, fmt, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
|
|
return r;
|
|
|
|
@@ -791,8 +793,8 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
|
|
|
|
|
|
|
|
|
|
len = info_print_ext_header(user->buf, sizeof(user->buf), r->info);
|
|
|
|
|
len += msg_print_ext_body(user->buf + len, sizeof(user->buf) - len,
|
|
|
|
|
&r->dict_buf[0], r->info->dict_len,
|
|
|
|
|
&r->text_buf[0], r->info->text_len);
|
|
|
|
|
&r->text_buf[0], r->info->text_len,
|
|
|
|
|
&r->info->dev_info);
|
|
|
|
|
|
|
|
|
|
user->seq = r->info->seq + 1;
|
|
|
|
|
logbuf_unlock_irq();
|
|
|
|
@@ -897,7 +899,7 @@ static int devkmsg_open(struct inode *inode, struct file *file)
|
|
|
|
|
|
|
|
|
|
prb_rec_init_rd(&user->record, &user->info,
|
|
|
|
|
&user->text_buf[0], sizeof(user->text_buf),
|
|
|
|
|
&user->dict_buf[0], sizeof(user->dict_buf));
|
|
|
|
|
NULL, 0);
|
|
|
|
|
|
|
|
|
|
logbuf_lock_irq();
|
|
|
|
|
user->seq = prb_first_valid_seq(prb);
|
|
|
|
@@ -941,6 +943,8 @@ const struct file_operations kmsg_fops = {
|
|
|
|
|
*/
|
|
|
|
|
void log_buf_vmcoreinfo_setup(void)
|
|
|
|
|
{
|
|
|
|
|
struct dev_printk_info *dev_info = NULL;
|
|
|
|
|
|
|
|
|
|
VMCOREINFO_SYMBOL(prb);
|
|
|
|
|
VMCOREINFO_SYMBOL(printk_rb_static);
|
|
|
|
|
VMCOREINFO_SYMBOL(clear_seq);
|
|
|
|
@@ -978,6 +982,13 @@ void log_buf_vmcoreinfo_setup(void)
|
|
|
|
|
VMCOREINFO_OFFSET(printk_info, text_len);
|
|
|
|
|
VMCOREINFO_OFFSET(printk_info, dict_len);
|
|
|
|
|
VMCOREINFO_OFFSET(printk_info, caller_id);
|
|
|
|
|
VMCOREINFO_OFFSET(printk_info, dev_info);
|
|
|
|
|
|
|
|
|
|
VMCOREINFO_STRUCT_SIZE(dev_printk_info);
|
|
|
|
|
VMCOREINFO_OFFSET(dev_printk_info, subsystem);
|
|
|
|
|
VMCOREINFO_LENGTH(printk_info_subsystem, sizeof(dev_info->subsystem));
|
|
|
|
|
VMCOREINFO_OFFSET(dev_printk_info, device);
|
|
|
|
|
VMCOREINFO_LENGTH(printk_info_device, sizeof(dev_info->device));
|
|
|
|
|
|
|
|
|
|
VMCOREINFO_STRUCT_SIZE(prb_data_ring);
|
|
|
|
|
VMCOREINFO_OFFSET(prb_data_ring, size_bits);
|
|
|
|
@@ -1070,22 +1081,19 @@ static unsigned int __init add_to_rb(struct printk_ringbuffer *rb,
|
|
|
|
|
struct prb_reserved_entry e;
|
|
|
|
|
struct printk_record dest_r;
|
|
|
|
|
|
|
|
|
|
prb_rec_init_wr(&dest_r, r->info->text_len, r->info->dict_len);
|
|
|
|
|
prb_rec_init_wr(&dest_r, r->info->text_len, 0);
|
|
|
|
|
|
|
|
|
|
if (!prb_reserve(&e, rb, &dest_r))
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
memcpy(&dest_r.text_buf[0], &r->text_buf[0], r->info->text_len);
|
|
|
|
|
dest_r.info->text_len = r->info->text_len;
|
|
|
|
|
if (dest_r.dict_buf) {
|
|
|
|
|
memcpy(&dest_r.dict_buf[0], &r->dict_buf[0], r->info->dict_len);
|
|
|
|
|
dest_r.info->dict_len = r->info->dict_len;
|
|
|
|
|
}
|
|
|
|
|
dest_r.info->facility = r->info->facility;
|
|
|
|
|
dest_r.info->level = r->info->level;
|
|
|
|
|
dest_r.info->flags = r->info->flags;
|
|
|
|
|
dest_r.info->ts_nsec = r->info->ts_nsec;
|
|
|
|
|
dest_r.info->caller_id = r->info->caller_id;
|
|
|
|
|
memcpy(&dest_r.info->dev_info, &r->info->dev_info, sizeof(dest_r.info->dev_info));
|
|
|
|
|
|
|
|
|
|
prb_final_commit(&e);
|
|
|
|
|
|
|
|
|
@@ -1093,7 +1101,6 @@ static unsigned int __init add_to_rb(struct printk_ringbuffer *rb,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char setup_text_buf[CONSOLE_EXT_LOG_MAX] __initdata;
|
|
|
|
|
static char setup_dict_buf[CONSOLE_EXT_LOG_MAX] __initdata;
|
|
|
|
|
|
|
|
|
|
void __init setup_log_buf(int early)
|
|
|
|
|
{
|
|
|
|
@@ -1165,7 +1172,7 @@ void __init setup_log_buf(int early)
|
|
|
|
|
|
|
|
|
|
prb_rec_init_rd(&r, &info,
|
|
|
|
|
&setup_text_buf[0], sizeof(setup_text_buf),
|
|
|
|
|
&setup_dict_buf[0], sizeof(setup_dict_buf));
|
|
|
|
|
NULL, 0);
|
|
|
|
|
|
|
|
|
|
prb_init(&printk_rb_dynamic,
|
|
|
|
|
new_log_buf, ilog2(new_log_buf_len),
|
|
|
|
@@ -1903,7 +1910,9 @@ static inline u32 printk_caller_id(void)
|
|
|
|
|
0x80000000 + raw_smp_processor_id();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t log_output(int facility, int level, enum log_flags lflags, const char *dict, size_t dictlen, char *text, size_t text_len)
|
|
|
|
|
static size_t log_output(int facility, int level, enum log_flags lflags,
|
|
|
|
|
const struct dev_printk_info *dev_info,
|
|
|
|
|
char *text, size_t text_len)
|
|
|
|
|
{
|
|
|
|
|
const u32 caller_id = printk_caller_id();
|
|
|
|
|
|
|
|
|
@@ -1927,12 +1936,12 @@ static size_t log_output(int facility, int level, enum log_flags lflags, const c
|
|
|
|
|
|
|
|
|
|
/* Store it in the record log */
|
|
|
|
|
return log_store(caller_id, facility, level, lflags, 0,
|
|
|
|
|
dict, dictlen, text, text_len);
|
|
|
|
|
dev_info, text, text_len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Must be called under logbuf_lock. */
|
|
|
|
|
int vprintk_store(int facility, int level,
|
|
|
|
|
const char *dict, size_t dictlen,
|
|
|
|
|
const struct dev_printk_info *dev_info,
|
|
|
|
|
const char *fmt, va_list args)
|
|
|
|
|
{
|
|
|
|
|
static char textbuf[LOG_LINE_MAX];
|
|
|
|
@@ -1974,15 +1983,14 @@ int vprintk_store(int facility, int level,
|
|
|
|
|
if (level == LOGLEVEL_DEFAULT)
|
|
|
|
|
level = default_message_loglevel;
|
|
|
|
|
|
|
|
|
|
if (dict)
|
|
|
|
|
if (dev_info)
|
|
|
|
|
lflags |= LOG_NEWLINE;
|
|
|
|
|
|
|
|
|
|
return log_output(facility, level, lflags,
|
|
|
|
|
dict, dictlen, text, text_len);
|
|
|
|
|
return log_output(facility, level, lflags, dev_info, text, text_len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
asmlinkage int vprintk_emit(int facility, int level,
|
|
|
|
|
const char *dict, size_t dictlen,
|
|
|
|
|
const struct dev_printk_info *dev_info,
|
|
|
|
|
const char *fmt, va_list args)
|
|
|
|
|
{
|
|
|
|
|
int printed_len;
|
|
|
|
@@ -2003,7 +2011,7 @@ asmlinkage int vprintk_emit(int facility, int level,
|
|
|
|
|
|
|
|
|
|
/* This stops the holder of console_sem just where we want him */
|
|
|
|
|
logbuf_lock_irqsave(flags);
|
|
|
|
|
printed_len = vprintk_store(facility, level, dict, dictlen, fmt, args);
|
|
|
|
|
printed_len = vprintk_store(facility, level, dev_info, fmt, args);
|
|
|
|
|
logbuf_unlock_irqrestore(flags);
|
|
|
|
|
|
|
|
|
|
/* If called from the scheduler, we can not call up(). */
|
|
|
|
@@ -2037,7 +2045,7 @@ EXPORT_SYMBOL(vprintk);
|
|
|
|
|
|
|
|
|
|
int vprintk_default(const char *fmt, va_list args)
|
|
|
|
|
{
|
|
|
|
|
return vprintk_emit(0, LOGLEVEL_DEFAULT, NULL, 0, fmt, args);
|
|
|
|
|
return vprintk_emit(0, LOGLEVEL_DEFAULT, NULL, fmt, args);
|
|
|
|
|
}
|
|
|
|
|
EXPORT_SYMBOL_GPL(vprintk_default);
|
|
|
|
|
|
|
|
|
@@ -2100,8 +2108,8 @@ static ssize_t info_print_ext_header(char *buf, size_t size,
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
static ssize_t msg_print_ext_body(char *buf, size_t size,
|
|
|
|
|
char *dict, size_t dict_len,
|
|
|
|
|
char *text, size_t text_len) { return 0; }
|
|
|
|
|
char *text, size_t text_len,
|
|
|
|
|
struct dev_printk_info *dev_info) { return 0; }
|
|
|
|
|
static void console_lock_spinning_enable(void) { }
|
|
|
|
|
static int console_lock_spinning_disable_and_check(void) { return 0; }
|
|
|
|
|
static void call_console_drivers(const char *ext_text, size_t ext_len,
|
|
|
|
@@ -2390,7 +2398,6 @@ void console_unlock(void)
|
|
|
|
|
{
|
|
|
|
|
static char ext_text[CONSOLE_EXT_LOG_MAX];
|
|
|
|
|
static char text[LOG_LINE_MAX + PREFIX_MAX];
|
|
|
|
|
static char dict[LOG_LINE_MAX];
|
|
|
|
|
unsigned long flags;
|
|
|
|
|
bool do_cond_resched, retry;
|
|
|
|
|
struct printk_info info;
|
|
|
|
@@ -2401,7 +2408,7 @@ void console_unlock(void)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prb_rec_init_rd(&r, &info, text, sizeof(text), dict, sizeof(dict));
|
|
|
|
|
prb_rec_init_rd(&r, &info, text, sizeof(text), NULL, 0);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Console drivers are called with interrupts disabled, so
|
|
|
|
@@ -2473,10 +2480,9 @@ skip:
|
|
|
|
|
r.info);
|
|
|
|
|
ext_len += msg_print_ext_body(ext_text + ext_len,
|
|
|
|
|
sizeof(ext_text) - ext_len,
|
|
|
|
|
&r.dict_buf[0],
|
|
|
|
|
r.info->dict_len,
|
|
|
|
|
&r.text_buf[0],
|
|
|
|
|
r.info->text_len);
|
|
|
|
|
r.info->text_len,
|
|
|
|
|
&r.info->dev_info);
|
|
|
|
|
}
|
|
|
|
|
len = record_print_text(&r,
|
|
|
|
|
console_msg_format & MSG_FORMAT_SYSLOG,
|
|
|
|
@@ -3055,7 +3061,7 @@ int vprintk_deferred(const char *fmt, va_list args)
|
|
|
|
|
{
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
r = vprintk_emit(0, LOGLEVEL_SCHED, NULL, 0, fmt, args);
|
|
|
|
|
r = vprintk_emit(0, LOGLEVEL_SCHED, NULL, fmt, args);
|
|
|
|
|
defer_console_output();
|
|
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|