mirror of
https://github.com/zeek/zeek.git
synced 2025-10-06 16:48:19 +00:00
Add checks to avoid improper negative values use.
This commit is contained in:
parent
a3b963ad4e
commit
a316878d01
12 changed files with 89 additions and 62 deletions
14
src/Expr.cc
14
src/Expr.cc
|
@ -3137,12 +3137,14 @@ FieldExpr::FieldExpr(Expr* arg_op, const char* arg_field_name)
|
||||||
{
|
{
|
||||||
RecordType* rt = op->Type()->AsRecordType();
|
RecordType* rt = op->Type()->AsRecordType();
|
||||||
field = rt->FieldOffset(field_name);
|
field = rt->FieldOffset(field_name);
|
||||||
td = rt->FieldDecl(field);
|
|
||||||
|
|
||||||
if ( field < 0 )
|
if ( field < 0 )
|
||||||
ExprError("no such field in record");
|
ExprError("no such field in record");
|
||||||
else
|
else
|
||||||
|
{
|
||||||
SetType(rt->FieldType(field)->Ref());
|
SetType(rt->FieldType(field)->Ref());
|
||||||
|
td = rt->FieldDecl(field);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3852,7 +3854,15 @@ void FieldAssignExpr::EvalIntoAggregate(const BroType* t, Val* aggr, Frame* f)
|
||||||
Val* v = op->Eval(f);
|
Val* v = op->Eval(f);
|
||||||
|
|
||||||
if ( v )
|
if ( v )
|
||||||
rec->Assign(rt->FieldOffset(field_name.c_str()), v);
|
{
|
||||||
|
int idx = rt->FieldOffset(field_name.c_str());
|
||||||
|
|
||||||
|
if ( idx < 0 )
|
||||||
|
reporter->InternalError("Missing record field: %s",
|
||||||
|
field_name.c_str());
|
||||||
|
|
||||||
|
rec->Assign(idx, v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int FieldAssignExpr::IsRecordElement(TypeDecl* td) const
|
int FieldAssignExpr::IsRecordElement(TypeDecl* td) const
|
||||||
|
|
10
src/Val.cc
10
src/Val.cc
|
@ -2670,6 +2670,16 @@ Val* RecordVal::LookupWithDefault(int field) const
|
||||||
return record_type->FieldDefault(field);
|
return record_type->FieldDefault(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Val* RecordVal::Lookup(const char* field, bool with_default) const
|
||||||
|
{
|
||||||
|
int idx = record_type->FieldOffset(field);
|
||||||
|
|
||||||
|
if ( idx < 0 )
|
||||||
|
reporter->InternalError("missing record field: %s", field);
|
||||||
|
|
||||||
|
return with_default ? LookupWithDefault(idx) : Lookup(idx);
|
||||||
|
}
|
||||||
|
|
||||||
RecordVal* RecordVal::CoerceTo(const RecordType* t, Val* aggr, bool allow_orphaning) const
|
RecordVal* RecordVal::CoerceTo(const RecordType* t, Val* aggr, bool allow_orphaning) const
|
||||||
{
|
{
|
||||||
if ( ! record_promotion_compatible(t->AsRecordType(), Type()->AsRecordType()) )
|
if ( ! record_promotion_compatible(t->AsRecordType(), Type()->AsRecordType()) )
|
||||||
|
|
11
src/Val.h
11
src/Val.h
|
@ -895,6 +895,17 @@ public:
|
||||||
Val* Lookup(int field) const; // Does not Ref() value.
|
Val* Lookup(int field) const; // Does not Ref() value.
|
||||||
Val* LookupWithDefault(int field) const; // Does Ref() value.
|
Val* LookupWithDefault(int field) const; // Does Ref() value.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up the value of a field by field name. If the field doesn't
|
||||||
|
* exist in the record type, it's an internal error: abort.
|
||||||
|
* @param field name of field to lookup.
|
||||||
|
* @param with_default whether to rely on field's &default attribute when
|
||||||
|
* the field has yet to be initialized.
|
||||||
|
* @return the value in field \a field. It is Ref()'d only if
|
||||||
|
* \a with_default is true.
|
||||||
|
*/
|
||||||
|
Val* Lookup(const char* field, bool with_default = false) const;
|
||||||
|
|
||||||
void Describe(ODesc* d) const;
|
void Describe(ODesc* d) const;
|
||||||
|
|
||||||
// This is an experiment to associate a BroObj within the
|
// This is an experiment to associate a BroObj within the
|
||||||
|
|
|
@ -54,17 +54,19 @@ void ConnSize_Analyzer::DeliverPacket(int len, const u_char* data, bool is_orig,
|
||||||
void ConnSize_Analyzer::UpdateConnVal(RecordVal *conn_val)
|
void ConnSize_Analyzer::UpdateConnVal(RecordVal *conn_val)
|
||||||
{
|
{
|
||||||
// RecordType *connection_type is decleared in NetVar.h
|
// RecordType *connection_type is decleared in NetVar.h
|
||||||
int orig_endp_idx = connection_type->FieldOffset("orig");
|
RecordVal *orig_endp = conn_val->Lookup("orig")->AsRecordVal();
|
||||||
int resp_endp_idx = connection_type->FieldOffset("resp");
|
RecordVal *resp_endp = conn_val->Lookup("resp")->AsRecordVal();
|
||||||
RecordVal *orig_endp = conn_val->Lookup(orig_endp_idx)->AsRecordVal();
|
|
||||||
RecordVal *resp_endp = conn_val->Lookup(resp_endp_idx)->AsRecordVal();
|
|
||||||
|
|
||||||
// endpoint is the RecordType from NetVar.h
|
// endpoint is the RecordType from NetVar.h
|
||||||
// TODO: or orig_endp->Type()->AsRecordVal()->FieldOffset()
|
|
||||||
int pktidx = endpoint->FieldOffset("num_pkts");
|
int pktidx = endpoint->FieldOffset("num_pkts");
|
||||||
int bytesidx = endpoint->FieldOffset("num_bytes_ip");
|
int bytesidx = endpoint->FieldOffset("num_bytes_ip");
|
||||||
|
|
||||||
// TODO: error handling?
|
if ( pktidx < 0 )
|
||||||
|
reporter->InternalError("'endpoint' record missing 'num_pkts' field");
|
||||||
|
|
||||||
|
if ( bytesidx < 0 )
|
||||||
|
reporter->InternalError("'endpoint' record missing 'num_bytes_ip' field");
|
||||||
|
|
||||||
orig_endp->Assign(pktidx, new Val(orig_pkts, TYPE_COUNT));
|
orig_endp->Assign(pktidx, new Val(orig_pkts, TYPE_COUNT));
|
||||||
orig_endp->Assign(bytesidx, new Val(orig_bytes, TYPE_COUNT));
|
orig_endp->Assign(bytesidx, new Val(orig_bytes, TYPE_COUNT));
|
||||||
resp_endp->Assign(pktidx, new Val(resp_pkts, TYPE_COUNT));
|
resp_endp->Assign(pktidx, new Val(resp_pkts, TYPE_COUNT));
|
||||||
|
|
|
@ -440,10 +440,8 @@ void ICMP_Analyzer::Describe(ODesc* d) const
|
||||||
|
|
||||||
void ICMP_Analyzer::UpdateConnVal(RecordVal *conn_val)
|
void ICMP_Analyzer::UpdateConnVal(RecordVal *conn_val)
|
||||||
{
|
{
|
||||||
int orig_endp_idx = connection_type->FieldOffset("orig");
|
RecordVal *orig_endp = conn_val->Lookup("orig")->AsRecordVal();
|
||||||
int resp_endp_idx = connection_type->FieldOffset("resp");
|
RecordVal *resp_endp = conn_val->Lookup("resp")->AsRecordVal();
|
||||||
RecordVal *orig_endp = conn_val->Lookup(orig_endp_idx)->AsRecordVal();
|
|
||||||
RecordVal *resp_endp = conn_val->Lookup(resp_endp_idx)->AsRecordVal();
|
|
||||||
|
|
||||||
UpdateEndpointVal(orig_endp, 1);
|
UpdateEndpointVal(orig_endp, 1);
|
||||||
UpdateEndpointVal(resp_endp, 0);
|
UpdateEndpointVal(resp_endp, 0);
|
||||||
|
|
|
@ -1102,11 +1102,8 @@ void TCP_Analyzer::FlipRoles()
|
||||||
|
|
||||||
void TCP_Analyzer::UpdateConnVal(RecordVal *conn_val)
|
void TCP_Analyzer::UpdateConnVal(RecordVal *conn_val)
|
||||||
{
|
{
|
||||||
int orig_endp_idx = connection_type->FieldOffset("orig");
|
RecordVal *orig_endp_val = conn_val->Lookup("orig")->AsRecordVal();
|
||||||
int resp_endp_idx = connection_type->FieldOffset("resp");
|
RecordVal *resp_endp_val = conn_val->Lookup("resp")->AsRecordVal();
|
||||||
|
|
||||||
RecordVal *orig_endp_val = conn_val->Lookup(orig_endp_idx)->AsRecordVal();
|
|
||||||
RecordVal *resp_endp_val = conn_val->Lookup(resp_endp_idx)->AsRecordVal();
|
|
||||||
|
|
||||||
orig_endp_val->Assign(0, new Val(orig->Size(), TYPE_COUNT));
|
orig_endp_val->Assign(0, new Val(orig->Size(), TYPE_COUNT));
|
||||||
orig_endp_val->Assign(1, new Val(int(orig->state), TYPE_COUNT));
|
orig_endp_val->Assign(1, new Val(int(orig->state), TYPE_COUNT));
|
||||||
|
|
|
@ -170,13 +170,9 @@ void UDP_Analyzer::DeliverPacket(int len, const u_char* data, bool is_orig,
|
||||||
|
|
||||||
void UDP_Analyzer::UpdateConnVal(RecordVal *conn_val)
|
void UDP_Analyzer::UpdateConnVal(RecordVal *conn_val)
|
||||||
{
|
{
|
||||||
int orig_endp_idx = connection_type->FieldOffset("orig");
|
RecordVal *orig_endp = conn_val->Lookup("orig")->AsRecordVal();
|
||||||
int resp_endp_idx = connection_type->FieldOffset("resp");
|
RecordVal *resp_endp = conn_val->Lookup("resp")->AsRecordVal();
|
||||||
RecordVal *orig_endp = conn_val->Lookup(orig_endp_idx)->AsRecordVal();
|
|
||||||
RecordVal *resp_endp = conn_val->Lookup(resp_endp_idx)->AsRecordVal();
|
|
||||||
|
|
||||||
orig_endp = conn_val->Lookup(orig_endp_idx)->AsRecordVal();
|
|
||||||
resp_endp = conn_val->Lookup(resp_endp_idx)->AsRecordVal();
|
|
||||||
UpdateEndpointVal(orig_endp, 1);
|
UpdateEndpointVal(orig_endp, 1);
|
||||||
UpdateEndpointVal(resp_endp, 0);
|
UpdateEndpointVal(resp_endp, 0);
|
||||||
|
|
||||||
|
|
|
@ -20,13 +20,8 @@ DataEvent::DataEvent(RecordVal* args, File* file,
|
||||||
|
|
||||||
file_analysis::Analyzer* DataEvent::Instantiate(RecordVal* args, File* file)
|
file_analysis::Analyzer* DataEvent::Instantiate(RecordVal* args, File* file)
|
||||||
{
|
{
|
||||||
using BifType::Record::Files::AnalyzerArgs;
|
Val* chunk_val = args->Lookup("chunk_event");
|
||||||
|
Val* stream_val = args->Lookup("stream_event");
|
||||||
int chunk_off = AnalyzerArgs->FieldOffset("chunk_event");
|
|
||||||
int stream_off = AnalyzerArgs->FieldOffset("stream_event");
|
|
||||||
|
|
||||||
Val* chunk_val = args->Lookup(chunk_off);
|
|
||||||
Val* stream_val = args->Lookup(stream_off);
|
|
||||||
|
|
||||||
if ( ! chunk_val && ! stream_val ) return 0;
|
if ( ! chunk_val && ! stream_val ) return 0;
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,7 @@ Extract::~Extract()
|
||||||
|
|
||||||
static Val* get_extract_field_val(RecordVal* args, const char* name)
|
static Val* get_extract_field_val(RecordVal* args, const char* name)
|
||||||
{
|
{
|
||||||
using BifType::Record::Files::AnalyzerArgs;
|
Val* rval = args->Lookup(name);
|
||||||
Val* rval = args->Lookup(AnalyzerArgs->FieldOffset(name));
|
|
||||||
|
|
||||||
if ( ! rval )
|
if ( ! rval )
|
||||||
reporter->Error("File extraction analyzer missing arg field: %s", name);
|
reporter->Error("File extraction analyzer missing arg field: %s", name);
|
||||||
|
|
|
@ -296,7 +296,7 @@ bool Manager::CreateStream(Stream* info, RecordVal* description)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Val* name_val = description->LookupWithDefault(rtype->FieldOffset("name"));
|
Val* name_val = description->Lookup("name", true);
|
||||||
string name = name_val->AsString()->CheckString();
|
string name = name_val->AsString()->CheckString();
|
||||||
Unref(name_val);
|
Unref(name_val);
|
||||||
|
|
||||||
|
@ -308,10 +308,10 @@ bool Manager::CreateStream(Stream* info, RecordVal* description)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnumVal* reader = description->LookupWithDefault(rtype->FieldOffset("reader"))->AsEnumVal();
|
EnumVal* reader = description->Lookup("reader", true)->AsEnumVal();
|
||||||
|
|
||||||
// get the source ...
|
// get the source ...
|
||||||
Val* sourceval = description->LookupWithDefault(rtype->FieldOffset("source"));
|
Val* sourceval = description->Lookup("source", true);
|
||||||
assert ( sourceval != 0 );
|
assert ( sourceval != 0 );
|
||||||
const BroString* bsource = sourceval->AsString();
|
const BroString* bsource = sourceval->AsString();
|
||||||
string source((const char*) bsource->Bytes(), bsource->Len());
|
string source((const char*) bsource->Bytes(), bsource->Len());
|
||||||
|
@ -321,7 +321,7 @@ bool Manager::CreateStream(Stream* info, RecordVal* description)
|
||||||
rinfo.source = copy_string(source.c_str());
|
rinfo.source = copy_string(source.c_str());
|
||||||
rinfo.name = copy_string(name.c_str());
|
rinfo.name = copy_string(name.c_str());
|
||||||
|
|
||||||
EnumVal* mode = description->LookupWithDefault(rtype->FieldOffset("mode"))->AsEnumVal();
|
EnumVal* mode = description->Lookup("mode", true)->AsEnumVal();
|
||||||
switch ( mode->InternalInt() )
|
switch ( mode->InternalInt() )
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -342,7 +342,7 @@ bool Manager::CreateStream(Stream* info, RecordVal* description)
|
||||||
|
|
||||||
Unref(mode);
|
Unref(mode);
|
||||||
|
|
||||||
Val* config = description->LookupWithDefault(rtype->FieldOffset("config"));
|
Val* config = description->Lookup("config", true);
|
||||||
info->config = config->AsTableVal(); // ref'd by LookupWithDefault
|
info->config = config->AsTableVal(); // ref'd by LookupWithDefault
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -401,11 +401,11 @@ bool Manager::CreateEventStream(RecordVal* fval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
RecordType *fields = fval->LookupWithDefault(rtype->FieldOffset("fields"))->AsType()->AsTypeType()->Type()->AsRecordType();
|
RecordType *fields = fval->Lookup("fields", true)->AsType()->AsTypeType()->Type()->AsRecordType();
|
||||||
|
|
||||||
Val *want_record = fval->LookupWithDefault(rtype->FieldOffset("want_record"));
|
Val *want_record = fval->Lookup("want_record", true);
|
||||||
|
|
||||||
Val* event_val = fval->LookupWithDefault(rtype->FieldOffset("ev"));
|
Val* event_val = fval->Lookup("ev", true);
|
||||||
Func* event = event_val->AsFunc();
|
Func* event = event_val->AsFunc();
|
||||||
Unref(event_val);
|
Unref(event_val);
|
||||||
|
|
||||||
|
@ -547,18 +547,18 @@ bool Manager::CreateTableStream(RecordVal* fval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Val* pred = fval->LookupWithDefault(rtype->FieldOffset("pred"));
|
Val* pred = fval->Lookup("pred", true);
|
||||||
|
|
||||||
RecordType *idx = fval->LookupWithDefault(rtype->FieldOffset("idx"))->AsType()->AsTypeType()->Type()->AsRecordType();
|
RecordType *idx = fval->Lookup("idx", true)->AsType()->AsTypeType()->Type()->AsRecordType();
|
||||||
RecordType *val = 0;
|
RecordType *val = 0;
|
||||||
|
|
||||||
if ( fval->LookupWithDefault(rtype->FieldOffset("val")) != 0 )
|
if ( fval->Lookup("val", true) != 0 )
|
||||||
{
|
{
|
||||||
val = fval->LookupWithDefault(rtype->FieldOffset("val"))->AsType()->AsTypeType()->Type()->AsRecordType();
|
val = fval->Lookup("val", true)->AsType()->AsTypeType()->Type()->AsRecordType();
|
||||||
Unref(val); // The lookupwithdefault in the if-clause ref'ed val.
|
Unref(val); // The lookupwithdefault in the if-clause ref'ed val.
|
||||||
}
|
}
|
||||||
|
|
||||||
TableVal *dst = fval->LookupWithDefault(rtype->FieldOffset("destination"))->AsTableVal();
|
TableVal *dst = fval->Lookup("destination", true)->AsTableVal();
|
||||||
|
|
||||||
// check if index fields match table description
|
// check if index fields match table description
|
||||||
int num = idx->NumFields();
|
int num = idx->NumFields();
|
||||||
|
@ -588,9 +588,9 @@ bool Manager::CreateTableStream(RecordVal* fval)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Val *want_record = fval->LookupWithDefault(rtype->FieldOffset("want_record"));
|
Val *want_record = fval->Lookup("want_record", true);
|
||||||
|
|
||||||
Val* event_val = fval->LookupWithDefault(rtype->FieldOffset("ev"));
|
Val* event_val = fval->Lookup("ev", true);
|
||||||
Func* event = event_val ? event_val->AsFunc() : 0;
|
Func* event = event_val ? event_val->AsFunc() : 0;
|
||||||
Unref(event_val);
|
Unref(event_val);
|
||||||
|
|
||||||
|
|
|
@ -295,7 +295,7 @@ bool Manager::CreateStream(EnumVal* id, RecordVal* sval)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordType* columns = sval->Lookup(rtype->FieldOffset("columns"))
|
RecordType* columns = sval->Lookup("columns")
|
||||||
->AsType()->AsTypeType()->Type()->AsRecordType();
|
->AsType()->AsTypeType()->Type()->AsRecordType();
|
||||||
|
|
||||||
bool log_attr_present = false;
|
bool log_attr_present = false;
|
||||||
|
@ -322,7 +322,7 @@ bool Manager::CreateStream(EnumVal* id, RecordVal* sval)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Val* event_val = sval->Lookup(rtype->FieldOffset("ev"));
|
Val* event_val = sval->Lookup("ev");
|
||||||
Func* event = event_val ? event_val->AsFunc() : 0;
|
Func* event = event_val ? event_val->AsFunc() : 0;
|
||||||
|
|
||||||
if ( event )
|
if ( event )
|
||||||
|
@ -579,19 +579,18 @@ bool Manager::AddFilter(EnumVal* id, RecordVal* fval)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Find the right writer type.
|
// Find the right writer type.
|
||||||
int idx = rtype->FieldOffset("writer");
|
EnumVal* writer = fval->Lookup("writer", true)->AsEnumVal();
|
||||||
EnumVal* writer = fval->LookupWithDefault(idx)->AsEnumVal();
|
|
||||||
|
|
||||||
// Create a new Filter instance.
|
// Create a new Filter instance.
|
||||||
|
|
||||||
Val* name = fval->LookupWithDefault(rtype->FieldOffset("name"));
|
Val* name = fval->Lookup("name", true);
|
||||||
Val* pred = fval->LookupWithDefault(rtype->FieldOffset("pred"));
|
Val* pred = fval->Lookup("pred", true);
|
||||||
Val* path_func = fval->LookupWithDefault(rtype->FieldOffset("path_func"));
|
Val* path_func = fval->Lookup("path_func", true);
|
||||||
Val* log_local = fval->LookupWithDefault(rtype->FieldOffset("log_local"));
|
Val* log_local = fval->Lookup("log_local", true);
|
||||||
Val* log_remote = fval->LookupWithDefault(rtype->FieldOffset("log_remote"));
|
Val* log_remote = fval->Lookup("log_remote", true);
|
||||||
Val* interv = fval->LookupWithDefault(rtype->FieldOffset("interv"));
|
Val* interv = fval->Lookup("interv", true);
|
||||||
Val* postprocessor = fval->LookupWithDefault(rtype->FieldOffset("postprocessor"));
|
Val* postprocessor = fval->Lookup("postprocessor", true);
|
||||||
Val* config = fval->LookupWithDefault(rtype->FieldOffset("config"));
|
Val* config = fval->Lookup("config", true);
|
||||||
|
|
||||||
Filter* filter = new Filter;
|
Filter* filter = new Filter;
|
||||||
filter->name = name->AsString()->CheckString();
|
filter->name = name->AsString()->CheckString();
|
||||||
|
@ -616,8 +615,8 @@ bool Manager::AddFilter(EnumVal* id, RecordVal* fval)
|
||||||
|
|
||||||
// Build the list of fields that the filter wants included, including
|
// Build the list of fields that the filter wants included, including
|
||||||
// potentially rolling out fields.
|
// potentially rolling out fields.
|
||||||
Val* include = fval->Lookup(rtype->FieldOffset("include"));
|
Val* include = fval->Lookup("include");
|
||||||
Val* exclude = fval->Lookup(rtype->FieldOffset("exclude"));
|
Val* exclude = fval->Lookup("exclude");
|
||||||
|
|
||||||
filter->num_fields = 0;
|
filter->num_fields = 0;
|
||||||
filter->fields = 0;
|
filter->fields = 0;
|
||||||
|
@ -631,7 +630,7 @@ bool Manager::AddFilter(EnumVal* id, RecordVal* fval)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the path for the filter.
|
// Get the path for the filter.
|
||||||
Val* path_val = fval->Lookup(rtype->FieldOffset("path"));
|
Val* path_val = fval->Lookup("path");
|
||||||
|
|
||||||
if ( path_val )
|
if ( path_val )
|
||||||
{
|
{
|
||||||
|
|
10
src/util.cc
10
src/util.cc
|
@ -1261,6 +1261,16 @@ void _set_processing_status(const char* status)
|
||||||
|
|
||||||
int fd = open(proc_status_file, O_CREAT | O_WRONLY | O_TRUNC, 0700);
|
int fd = open(proc_status_file, O_CREAT | O_WRONLY | O_TRUNC, 0700);
|
||||||
|
|
||||||
|
if ( fd < 0 )
|
||||||
|
{
|
||||||
|
char buf[256];
|
||||||
|
strerror_r(errno, buf, sizeof(buf));
|
||||||
|
reporter->Error("Failed to open process status file '%s': %s",
|
||||||
|
proc_status_file, buf);
|
||||||
|
errno = old_errno;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int len = strlen(status);
|
int len = strlen(status);
|
||||||
while ( len )
|
while ( len )
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue