#help_index "File/Cmd Line (Typically);Cmd Line (Typically)"
public U8 *DBlk(I64 blk, Bool write=FALSE)
{//Dump disk block. Optionally, write.
//If you set write to TRUE, the block will
    //be written when you press <ESC>.
    //See ::/Demo/Disk/DiskRaw.CC.
    U8 *buf = MAlloc(BLK_SIZE);

    BlkRead(Fs->cur_dv, buf, blk, 1);
    DocD(buf, BLK_SIZE);
    if (write)
    {
        "Edit and press <ESC> to write or <SHIFT-ESC>\n";
        if (View)
        {
            "Write\n";
            BlkWrite(Fs->cur_dv, buf, blk, 1);
        }
    }

    return buf;
}

public U8 *DClus(I64 c, Bool write=FALSE, I64 num=0)
{//Dump disk clus. Optionally, write.
//If you set write to TRUE, the clus will
    //be written when you press <ESC>.
    //See ::/Demo/Disk/DiskRaw.CC.
    //Do Dir("*",TRUE); to get clus numbers of files.
    U8 *buf = MAlloc(Fs->cur_dv->spc << BLK_SIZE_BITS);

    c = ClusNumNext(Fs->cur_dv, c, num);
    ClusRead(Fs->cur_dv, buf, c, 1);
    "Clus:%X\n", c;
    DocD(buf, Fs->cur_dv->spc << BLK_SIZE_BITS);
    if (write)
    {
        "Edit and press <ESC> to write or <SHIFT-ESC>\n";
        if (View)
        {
            "Write\n";
            ClusWrite(Fs->cur_dv, buf, c, 1);
        }
    }

    return buf;
}

public U8 *Dump(U8 *filename, Bool write=FALSE)
{//Dump file. Optionally, write.
//If you set write to TRUE, the file will
    //be written when you press <ESC>.
    U8 *buf;
    I64 size;

    if (buf = FileRead(filename,&size))
    {
        DocD(buf, size);
        if (write)
        {
            "Edit and press <ESC> to write or <SHIFT-ESC>\n";
            if (View)
            {
                "Write\n";
                FileWrite(filename, buf, size);
            }
        }
    }

    return buf;
}

public Bool Copy(U8 *src_files_find_mask, U8 *dst_files_find_mask=".")
{//Copy files.
//If the name ends in ".Z", it will
    //be stored compressed.  If not ".Z"
    //it will be stored uncompressed.
    Bool         res = TRUE;
    CDirContext *dirc;
    CDirEntry   *tmpde, *tmpde1;
    U8          *st;

    if (!(tmpde1 = FilesFind(src_files_find_mask, FUF_CLUS_ORDER)))
        return FALSE;
    if (IsDir(dst_files_find_mask))
    {
        if (dirc = DirContextNew(dst_files_find_mask, TRUE))
        {
            tmpde = tmpde1;
            while (tmpde)
            {
                if (!(tmpde->attr & RS_ATTR_DIR))
                {
                    st = FileNameAbs(tmpde->name);
                    if (!CopySingle(tmpde->full_name, st))
                        res = FALSE;
                    Free(st);
                }
                tmpde = tmpde->next;
            }
            DirContextDel(dirc);
        }
        DirTreeDel(tmpde1);
        return res;
    }
    else
    {
        DirTreeDel(tmpde1);
        return CopySingle(src_files_find_mask, dst_files_find_mask);
    }
}

public Bool Move(U8 *f1, U8 *f2)
{//Move files from one location to another or rename.
    if (Copy(f1, f2))
    {
        Del(f1);
        return TRUE;
    }

    return FALSE;
}

I64 CopyTree2(CDirEntry *tmpde, I64 src_dir_len, I64 dst_dir_len, U8 *dst_dir)
{
    U8 *st;
    I64 res = 1;

    while (tmpde)
    {
        st = MAlloc(StrLen(tmpde->full_name) + dst_dir_len + 2);
        MemCopy(st, dst_dir, dst_dir_len);
        StrCopy(st + dst_dir_len, tmpde->full_name + src_dir_len);
        if (tmpde->attr & RS_ATTR_DIR)
        {
            DirMake(st, LinkedListCount(tmpde->sub));
            res += CopyTree2(tmpde->sub, src_dir_len, dst_dir_len, dst_dir);
        }
        else
            if (CopySingle(tmpde->full_name, st))
                res++;
        Free(st);
        tmpde = tmpde->next;
    }

    return res;
}
public I64 CopyTree(U8 *src_files_find_mask, U8 *dst_files_find_mask, Bool no_mask=TRUE)
{//Copy directory tree.
//Returns the count of copied files (not dirs).
    CDirContext *dirc;
    CDirEntry   *tmpde = NULL;
    I64          res = 0, i1, i2;
    U8          *st1, *st2;

    st1 = DirNameAbs(src_files_find_mask);
    st2 = DirNameAbs(dst_files_find_mask);
    i1 = StrLen(st1);
    if (!StrNCompare(st1, st2, i1) && (st2[i1] == '/' || !st2[i1]))
    {
        Free(st1);
        Free(st2);
        return 0;
    }
    Free(st1);
    Free(st2);
    if (dirc = DirContextNew(src_files_find_mask, TRUE,, no_mask))
    {
        tmpde = FilesFind(dirc->mask, FUF_RECURSE);
        st1 = DirCur;
        DirContextDel(dirc);
        i1 = StrLen(st1);
        if (i1 == 3)
            i1--;
        if (dirc = DirContextNew(dst_files_find_mask, TRUE, TRUE))
        {
            st2 = DirCur;
            i2 = StrLen(st2);
            if (i2 == 3)
                i2--;
            res = CopyTree2(tmpde, i1, i2, st2);
            DirContextDel(dirc);
            Free(st2);
        }
        DirTreeDel(tmpde);
        Free(st1);
    }

    return res;
}

I64 DelTreeDirs(CDirEntry *tmpde1)
{
    I64          res = 0;
    CDirEntry   *tmpde2;

    while (tmpde1)
    {
        tmpde2 = tmpde1->next;
        if (tmpde1->attr & RS_ATTR_DIR)
        {
            if (tmpde1->sub)
                res += DelTreeDirs(tmpde1->sub);
            res += Del(tmpde1->full_name, TRUE, TRUE);
        }
        DirEntryDel(tmpde1);
        tmpde1 = tmpde2;
    }

    return res;
}
I64 DelTreeFiles(CDirEntry *tmpde1)
{
    I64          res = 0;
    CDirEntry   *tmpde2;

    while (tmpde1)
    {
        tmpde2 = tmpde1->next;
        if (tmpde1->attr & RS_ATTR_DIR)
        {
            if (tmpde1->sub)
                res += DelTreeFiles(tmpde1->sub);
        }
        else
            res += Del(tmpde1->full_name, FALSE, TRUE);
        DirEntryDel(tmpde1);
        tmpde1 = tmpde2;
    }

    return res;
}
public I64 DelTree(U8 *files_find_mask, U8 *fu_flags=NULL)
{//Delete directory tree.
    I64 res = 0, fuf_flags = 0;

    FlagsScan(&fuf_flags, Define("ST_FILE_UTIL_FLAGS"), "+r");
    FlagsScan(&fuf_flags, Define("ST_FILE_UTIL_FLAGS"), fu_flags);
    if (IsDir(files_find_mask))
    {
        res = DelTreeDirs(FilesFind(files_find_mask, fuf_flags));
        res += Del(files_find_mask, TRUE, TRUE);
        res += Del(files_find_mask, FALSE, TRUE);
    }
    else
        res = DelTreeFiles(FilesFind(files_find_mask, fuf_flags));

    return res;
}

U0 TouchFile(U8 *filename, U8 *attr, CDate cdt=I64_MIN)
{
    CDrive      *drive = Letter2Drive(*filename);
    CDirEntry    de;
    U8          *cur_dir = StrNew(filename), buf[STR_LEN];

    if (FileFind(filename, &de, FUF_JUST_FILES))
    {
        Free(de.full_name);
        if (!StrCompare(attr, "+?"))
            "%-48ts %s\n", filename, FlagsStrPrint(buf, Define("ST_FILE_ATTRS"), de.attr);
        else
        {
            StrFirstRemove(cur_dir, ":");
            StrLastRemove(cur_dir, "/");
            if (!*cur_dir)
                StrCopy(cur_dir, "/");
            FlagsScan(&de.attr, Define("ST_FILE_ATTRS"), attr);
            if (cdt == I64_MIN)
                de.datetime = Now;
            else
                de.datetime = cdt;
            DirNew(drive, cur_dir, &de, FALSE);
        }
    }
    else
        PrintErr("File not found: \"%s\".\n", filename);
    Free(cur_dir);
}
public U0 Touch(U8 *files_find_mask="*", U8 *attr="+?", U8 *fu_flags=NULL, CDate cdt=I64_MIN)
{/*Touch file attributes and DateTime.
Default lists attributes.
attr: "+?" =show current
"+T" =resident
RS_ATTR_READ_ONLY  ST_FILE_ATTRS
To Set DateL:
Touch(filename,"",,datetime);
*/
    I64          fuf_flags = 0;
    CDirEntry   *tmpde, *tmpde1;

    FlagsScan(&fuf_flags, Define("ST_FILE_UTIL_FLAGS"), "+f+F");
    FlagsScan(&fuf_flags, Define("ST_FILE_UTIL_FLAGS"), fu_flags);
    tmpde=tmpde1 = FilesFind(files_find_mask, fuf_flags);
    while (tmpde)
    {
        TouchFile(tmpde->full_name, attr, cdt);
        tmpde = tmpde->next;
    }
    DirTreeDel(tmpde1);
}