str_list* _std str_copylines(str_list*list, u32t first, u32t last) {
   TStrings lst;
   if (first<=last) {
      str_getstrs(list,lst);
      if (lst.Count())
         if (first>=lst.Count()) lst.Clear(); else {
            if (last>=lst.Count()) last=lst.Max();
            if (++last!=lst.Count()) lst.Delete(last,lst.Count()-last);
            if (first) lst.Delete(0,first);
         }
   }   
   return str_getlist_local(lst.Str);
}
/** return FS detection list.
    Function returns parsed [fsdetect] section contents, in the single module
    owned heap block. Last entry in this list is zero-filled */
fs_detect_list* ecmd_readfsdetect() {
   TStrings lst;
   if (!ecmd_readsec("fsdetect",lst)) return 0;

   u32t ii, blen = 0, nlen = 0, ecnt = 0;
   for (ii=0; ii<lst.Count(); ii++) {
      // filter out commented lines
      if (lst[ii][0]!=';') {
         spstr val = lst.Value(ii);
         if (val.words(",")==2) {
            blen += val.word(2,",").words();
            nlen += lst.Name(ii).trim().length()+1;
            ecnt++;
            continue;
         }
      }
      lst[ii].clear();
   }
   if (!blen || !nlen) return 0;
   // alloc/calc block pointers
   u32t        listlen = sizeof(fs_detect_list)*(ecnt+1), idx;
   fs_detect_list *res = (fs_detect_list*)malloc(listlen+blen+nlen);
   char          *nptr = (char*)res + listlen;
   u8t           *dptr = (u8t*)nptr + nlen;
   // zero last entry
   memset(res+ecnt, 0, sizeof(fs_detect_list));
   // build return information in the single memory block
   for (ii=0, idx=0; ii<lst.Count(); ii++)
      if (lst[ii].length()) {
         spstr  val = lst.Value(ii),
                key = lst.Name(ii).trim();
         spstr bstr = val.word(2,",");

         res[idx].fsname  = nptr;
         res[idx].offset  = val.word_Dword(1,",");
         res[idx].size    = bstr.words();
         res[idx].cmpdata = dptr;
         // fa name
         memcpy(nptr, key(), key.length()+1);
         nptr += key.length()+1;
         // binary data to cmp
         for (u32t ll=0; ll<res[idx].size; ll++)
            *dptr++ = strtoul(bstr() + bstr.wordpos(ll+1), 0, 16);
         idx++;
      }
   return res;
}
void ecmd_commands(TStrings &rc) {
   MTLOCK_THIS_FUNC _lk;
   if (!ecmd) ecmd = new_ini(ecmd_name());
   ecmd->ReadSectionKeys("COMMANDS",rc);
   // trim spaces and empty lines
   rc.TrimEmptyLines();
   rc.TrimAllLines();
   for (int ii=0; ii<rc.Count(); ii++) rc[ii].upper();
}
static void init_msg_ini(void) {
   if (!msg) {
      TStrings     ht;
      spstr   secname("help");
      msg = new_ini(msg_name());

      msg->ReadSection(secname, ht);
      /* merge strings, ended by backslash. ugly, but allows more friendly
         editing of msg.ini */
      l ii=0, lp;
      while (ii<ht.Count()) {
         if (ht[ii].trim().lastchar()=='\\') {
            ht[ii].dellast()+=ht.MergeBackSlash(ii+1,&lp);
            ht.Delete(ii+1, lp-ii-1);
         }
         ii++;
      }
      msg->WriteSection(secname, ht, true);
   }
}
static void _std getlog(log_header *log, void *extptr) {
   getloginfo *pli = (getloginfo*)extptr;
   int    puretext = (pli->flags&(LOGTF_DATE|LOGTF_TIME|LOGTF_LEVEL))==0;

   if (puretext) {
      spstr ltext;
      while (log->offset) {
         if ((log->flags&LOGIF_USED)!=0 && ((pli->flags&LOGTF_LEVEL)==0 ||
           (pli->flags&LOGTF_LEVELMASK)>=(log->flags&LOGIF_LEVELMASK)))
         {
            ltext+=(char*)(log+1);
         }
         log+=log->offset;
      }
      if (pli->flags&LOGTF_DOSTEXT) ltext.replace("\n","\r\n");
      // make copy a bit faster
      pli->rc = (char*)malloc(ltext.length()+1);
      memcpy(pli->rc, ltext(), ltext.length());
      pli->rc[ltext.length()] = 0;
   } else {
      TStrings    lst;
      struct tm    dt;
      u32t      pdtme = 0;
      time_t     ptme;

      while (log->offset) {
         if ((log->flags&LOGIF_USED)!=0 && ((pli->flags&LOGTF_LEVEL)==0 ||
           (pli->flags&LOGTF_LEVELMASK)>=(log->flags&LOGIF_LEVELMASK)))
         {
            if (pdtme!=log->dostime) {
               dostimetotm(pdtme=log->dostime,&dt);
               ptme = mktime(&dt);
            }
            spstr estr, astr;

            if (pli->flags&LOGTF_DATE)
               estr.sprintf("%02d.%02d.%02d ",dt.tm_mday,dt.tm_mon+1,dt.tm_year-100);
            if (pli->flags&LOGTF_TIME) {
               int sec = dt.tm_sec;
               if (log->flags&LOGIF_SECOND) sec++;
               astr.sprintf("%02d:%02d:%02d ",dt.tm_hour, dt.tm_min, sec);
               estr+=astr;
            }
            if (pli->flags&LOGTF_LEVEL) {
               if (pli->flags&LOGTF_FLAGS)
                  astr.sprintf("[%c%d%c] ",log->flags&LOGIF_REALMODE?'r':' ',
                     log->flags&LOGIF_LEVELMASK,log->flags&LOGIF_DELAY?'d':' ');
               else
                  astr.sprintf("[%d] ",log->flags&LOGIF_LEVELMASK);
               estr+=astr;
            }
            estr+=(char*)(log+1);
            if (estr.lastchar()=='\n') estr.dellast();
            lst.AddObject(estr,ptme);
         }
         log+=log->offset;
      }
      int ii,jj;
      const char *eol = pli->flags&LOGTF_DOSTEXT?"\r\n":"\n";

      log_it(2, "%d lines of log queried\n", lst.Count());

      for (ii=1;ii<lst.Count();ii++)
         // we`re can`t fill entire log for 3 seconds
         if (lst.Objects(ii-1)-lst.Objects(ii)>3) break;
      // re-order cyclic added lines
      if (ii<lst.Count()) {
         spstr t1 = lst.GetTextToStr(eol,ii),
               t2 = lst.GetTextToStr(eol,0,ii);
         pli->rc = (char*)malloc(t1.length()+t2.length()+1);
         memcpy(pli->rc, t1(), t1.length());
         memcpy(pli->rc+t1.length(), t2(), t2.length()+1);
      } else
         pli->rc = lst.GetText(eol);
   }
}