Added embedded filesystem. Whee!
This commit is contained in:
@@ -134,6 +134,15 @@ flash: firmware/0x00000.bin firmware/0x40000.bin
|
|||||||
sleep 3
|
sleep 3
|
||||||
-$(ESPTOOL) --port $(ESPPORT) write_flash 0x40000 firmware/0x40000.bin
|
-$(ESPTOOL) --port $(ESPPORT) write_flash 0x40000 firmware/0x40000.bin
|
||||||
|
|
||||||
|
webpages.espfs: html/ mkespfsimage/mkespfsimage
|
||||||
|
cd html; find | ../mkespfsimage/mkespfsimage > ../webpages.espfs; cd ..
|
||||||
|
|
||||||
|
mkespfsimage/mkespfsimage: mkespfsimage/
|
||||||
|
make -C mkespfsimage
|
||||||
|
|
||||||
|
htmlflash: webpages.espfs
|
||||||
|
-$(ESPTOOL) --port $(ESPPORT) write_flash 0x20000 webpages.espfs
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(Q) rm -f $(APP_AR)
|
$(Q) rm -f $(APP_AR)
|
||||||
$(Q) rm -f $(TARGET_OUT)
|
$(Q) rm -f $(TARGET_OUT)
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<html>
|
||||||
|
<head><title>Test</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>Test!</h1>
|
||||||
|
<p>Yay, the cpio filesystem thingy works. Click <a href="/test2.html">here</a> to see
|
||||||
|
if the 2nd page works too.
|
||||||
|
</p>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<html>
|
||||||
|
<head><title>Test</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>Test2!</h1>
|
||||||
|
<p>Here's an image of a cat (hopefully...)<br />
|
||||||
|
<img src="cat.jpeg">
|
||||||
|
</p>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
mkespfsimage: main.o
|
||||||
|
$(CC) -o mkespfsimage $<
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef ESPROFSFORMAT_H
|
||||||
|
#define ESPROFSFORMAT_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
Stupid cpio-like tool to make read-only 'filesystems' that live on the flash SPI chip of the module.
|
||||||
|
Can (will) use lzf compression (when I come around to it) to make shit quicker. Aligns names, files,
|
||||||
|
headers on 4-byte boundaries so the SPI abstraction hardware in the ESP8266 doesn't crap on itself
|
||||||
|
when trying to do a <4byte or unaligned read.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
The idea 'borrows' from cpio: it's basically a concatenation of {header, filename, file} data.
|
||||||
|
Header, filename and file data is 32-bit aligned. The last file is indicated by data-less header
|
||||||
|
with the FLAG_LASTFILE flag set.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#define FLAG_LASTFILE (1<<0)
|
||||||
|
#define COMPRESS_NONE 0
|
||||||
|
#define COMPRESS_LZF 1
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t magic;
|
||||||
|
int8_t flags;
|
||||||
|
int8_t compression;
|
||||||
|
int16_t nameLen;
|
||||||
|
int32_t fileLenComp;
|
||||||
|
int32_t fileLenDecomp;
|
||||||
|
} EspFsHeader;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "espfsformat.h"
|
||||||
|
|
||||||
|
//Routines to convert host format to the endianness used in the xtensa
|
||||||
|
short htoxs(short in) {
|
||||||
|
char r[2];
|
||||||
|
r[0]=in;
|
||||||
|
r[1]=in>>8;
|
||||||
|
return *((short *)r);
|
||||||
|
}
|
||||||
|
|
||||||
|
int htoxl(int in) {
|
||||||
|
unsigned char r[4];
|
||||||
|
r[0]=in;
|
||||||
|
r[1]=in>>8;
|
||||||
|
r[2]=in>>16;
|
||||||
|
r[3]=in>>24;
|
||||||
|
return *((int *)r);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void handleFile(int f, char *name) {
|
||||||
|
char *fdat;
|
||||||
|
off_t size;
|
||||||
|
EspFsHeader h;
|
||||||
|
int nameLen;
|
||||||
|
size=lseek(f, 0, SEEK_END);
|
||||||
|
fdat=mmap(NULL, size, PROT_READ, MAP_SHARED, f, 0);
|
||||||
|
if (fdat==MAP_FAILED) {
|
||||||
|
perror("mmap");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fill header data
|
||||||
|
h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24);
|
||||||
|
h.flags=0;
|
||||||
|
h.compression=COMPRESS_NONE;
|
||||||
|
nameLen=strlen(name)+1;
|
||||||
|
if (nameLen&3) nameLen+=4-(nameLen&3); //Round to next 32bit boundary
|
||||||
|
h.nameLen=htoxs(nameLen);
|
||||||
|
h.fileLenComp=htoxl(size);
|
||||||
|
h.fileLenDecomp=htoxl(size);
|
||||||
|
|
||||||
|
write(1, &h, sizeof(EspFsHeader));
|
||||||
|
write(1, name, nameLen); //ToDo: this can eat up a few bytes after the buffer.
|
||||||
|
write(1, fdat, size);
|
||||||
|
//Pad out to 32bit boundary
|
||||||
|
while (size&3) {
|
||||||
|
write(1, "\000", 1);
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
munmap(fdat, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write final dummy header with FLAG_LASTFILE set.
|
||||||
|
void finishArchive() {
|
||||||
|
EspFsHeader h;
|
||||||
|
h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24);
|
||||||
|
h.flags=FLAG_LASTFILE;
|
||||||
|
h.compression=COMPRESS_NONE;
|
||||||
|
h.nameLen=htoxs(0);
|
||||||
|
h.fileLenComp=htoxl(0);
|
||||||
|
h.fileLenDecomp=htoxl(0);
|
||||||
|
write(1, &h, sizeof(EspFsHeader));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
int f;
|
||||||
|
char fileName[1024];
|
||||||
|
char *realName;
|
||||||
|
struct stat statBuf;
|
||||||
|
int serr;
|
||||||
|
while(fgets(fileName, sizeof(fileName), stdin)) {
|
||||||
|
//Kill off '\n' at the end
|
||||||
|
fileName[strlen(fileName)-1]=0;
|
||||||
|
//Only include files
|
||||||
|
serr=stat(fileName, &statBuf);
|
||||||
|
if ((serr==0) && S_ISREG(statBuf.st_mode)) {
|
||||||
|
//Strip off './' or '/' madness.
|
||||||
|
realName=fileName;
|
||||||
|
if (fileName[0]=='.') realName++;
|
||||||
|
if (realName[0]=='/') realName++;
|
||||||
|
f=open(fileName, O_RDONLY);
|
||||||
|
if (f>0) {
|
||||||
|
handleFile(f, realName);
|
||||||
|
fprintf(stderr, "%s\n", realName);
|
||||||
|
close(f);
|
||||||
|
} else {
|
||||||
|
perror(fileName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (serr!=0) {
|
||||||
|
perror(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finishArchive();
|
||||||
|
}
|
||||||
|
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
#include "driver/uart.h"
|
||||||
|
#include "c_types.h"
|
||||||
|
#include "user_interface.h"
|
||||||
|
#include "espconn.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "osapi.h"
|
||||||
|
#include "../mkespfsimage/espfsformat.h"
|
||||||
|
#include "espfs.h"
|
||||||
|
|
||||||
|
struct EspFsFile {
|
||||||
|
EspFsHeader *header;
|
||||||
|
char decompressor;
|
||||||
|
int32_t posDecomp;
|
||||||
|
char *posStart;
|
||||||
|
char *posComp;
|
||||||
|
void *decompData;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Available locations, at least in my flash, with boundaries partially guessed:
|
||||||
|
0x00000 (0x10000): Code/data (RAM data?)
|
||||||
|
0x10000 (0x30000): Free (filled with zeroes) (parts used by ESPCloud and maybe SSL)
|
||||||
|
0x40000 (0x20000): Code/data (ROM data?)
|
||||||
|
0x60000 (0x1C000): Free
|
||||||
|
0x7c000 (0x04000): Param store
|
||||||
|
0x80000 - end of flash
|
||||||
|
|
||||||
|
Accessing the flash through the mem emulation at 0x40200000 is a bit hairy: All accesses
|
||||||
|
*must* be aligned 32-bit accesses. Reading a short, byte or unaligned word will result in
|
||||||
|
a memory exception, crashing the program.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
//Copies len bytes over from dst to src, but does it using *only*
|
||||||
|
//aligned 32-bit reads.
|
||||||
|
void memcpyAligned(char *dst, char *src, int len) {
|
||||||
|
int x;
|
||||||
|
int w, b;
|
||||||
|
for (x=0; x<len; x++) {
|
||||||
|
b=((int)src&3);
|
||||||
|
w=*((int *)(src-b));
|
||||||
|
if (b==0) *dst=(w>>0);
|
||||||
|
if (b==1) *dst=(w>>8);
|
||||||
|
if (b==2) *dst=(w>>16);
|
||||||
|
if (b==3) *dst=(w>>24);
|
||||||
|
dst++; src++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EspFsFile *espFsOpen(char *fileName) {
|
||||||
|
char *p=(char *)(ESPFS_POS+0x40200000);
|
||||||
|
char *hpos;
|
||||||
|
char namebuf[256];
|
||||||
|
EspFsHeader h;
|
||||||
|
EspFsFile *r;
|
||||||
|
//Skip initial slashes
|
||||||
|
while(fileName[0]=='/') fileName++;
|
||||||
|
//Go find that file!
|
||||||
|
while(1) {
|
||||||
|
hpos=p;
|
||||||
|
os_memcpy(&h, p, sizeof(EspFsHeader));
|
||||||
|
//ToDo: check magic
|
||||||
|
if (h.flags&FLAG_LASTFILE) {
|
||||||
|
// os_printf("End of archive.\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
p+=sizeof(EspFsHeader);
|
||||||
|
os_memcpy(namebuf, p, sizeof(namebuf));
|
||||||
|
// os_printf("Found file %s . Namelen=%x fileLen=%x\n", namebuf, h.nameLen,h.fileLenComp);
|
||||||
|
if (os_strcmp(namebuf, fileName)==0) {
|
||||||
|
p+=h.nameLen;
|
||||||
|
r=(EspFsFile *)os_malloc(sizeof(EspFsFile));
|
||||||
|
if (r==NULL) return NULL;
|
||||||
|
r->header=(EspFsHeader *)hpos;
|
||||||
|
r->decompressor=h.compression;
|
||||||
|
r->posComp=p;
|
||||||
|
r->posStart=p;
|
||||||
|
r->posDecomp=0;
|
||||||
|
r->decompData=NULL;
|
||||||
|
//ToDo: Init any decompressor
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
//Skip name and file
|
||||||
|
p+=h.nameLen+h.fileLenComp;
|
||||||
|
if ((int)p&3) p+=4-((int)p&3); //align to next 32bit val
|
||||||
|
// os_printf("Next addr = %x\n", (int)p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int espFsRead(EspFsFile *fh, char *buff, int len) {
|
||||||
|
if (fh==NULL) return 0;
|
||||||
|
if (fh->decompressor==COMPRESS_NONE) {
|
||||||
|
int toRead;
|
||||||
|
toRead=fh->header->fileLenComp-(fh->posComp-fh->posStart);
|
||||||
|
if (len>toRead) len=toRead;
|
||||||
|
// os_printf("Reading %d bytes from %x\n", len, fh->posComp);
|
||||||
|
memcpyAligned(buff, fh->posComp, len);
|
||||||
|
fh->posDecomp+=len;
|
||||||
|
fh->posComp+=len;
|
||||||
|
// os_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void espFsClose(EspFsFile *fh) {
|
||||||
|
if (fh==NULL) return;
|
||||||
|
os_free(fh);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef ESPFS_H
|
||||||
|
#define ESPFS_H
|
||||||
|
|
||||||
|
//Pos of esp fs in flash
|
||||||
|
#define ESPFS_POS 0x20000
|
||||||
|
|
||||||
|
typedef struct EspFsFile EspFsFile;
|
||||||
|
|
||||||
|
EspFsFile *espFsOpen(char *fileName);
|
||||||
|
int espFsRead(EspFsFile *fh, char *buff, int len);
|
||||||
|
void espFsClose(EspFsFile *fh);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
+56
-14
@@ -8,18 +8,34 @@
|
|||||||
#include "espconn.h"
|
#include "espconn.h"
|
||||||
#include "httpd.h"
|
#include "httpd.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
|
#include "espfs.h"
|
||||||
|
|
||||||
#define MAX_HEAD_LEN 1024
|
#define MAX_HEAD_LEN 1024
|
||||||
|
|
||||||
int cgiSet(struct espconn *conn);
|
//struct UrlData;
|
||||||
|
typedef struct UrlData UrlData;
|
||||||
typedef int (* cgiSendCallback)(struct espconn *conn);
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
char *url;
|
||||||
|
char *getArgs;
|
||||||
|
const UrlData *effUrl;
|
||||||
|
char *datPtr;
|
||||||
|
int datLen;
|
||||||
|
EspFsFile *file;
|
||||||
|
} GetData;
|
||||||
|
|
||||||
|
typedef int (* cgiSendCallback)(struct espconn *conn, GetData *getData);
|
||||||
|
|
||||||
|
struct UrlData {
|
||||||
const char *url;
|
const char *url;
|
||||||
const char *fixedResp;
|
const char *fixedResp;
|
||||||
cgiSendCallback cgiCb;
|
cgiSendCallback cgiCb;
|
||||||
} UrlData;
|
};
|
||||||
|
|
||||||
|
int cgiSet(struct espconn *conn, GetData *getData);
|
||||||
|
int cgiGetFlash(struct espconn *conn, GetData *getData);
|
||||||
|
static int cgiSendFile(struct espconn *conn, GetData *getData);
|
||||||
|
|
||||||
|
|
||||||
const char htmlIndex[]="<html><head><title>Hello World</title></head> \
|
const char htmlIndex[]="<html><head><title>Hello World</title></head> \
|
||||||
<body><h1>Hello, World!</h1></body> \
|
<body><h1>Hello, World!</h1></body> \
|
||||||
@@ -29,21 +45,18 @@ const char htmlIndex[]="<html><head><title>Hello World</title></head> \
|
|||||||
static const UrlData urls[]={
|
static const UrlData urls[]={
|
||||||
{"/", htmlIndex, NULL},
|
{"/", htmlIndex, NULL},
|
||||||
{"/set", NULL, cgiSet},
|
{"/set", NULL, cgiSet},
|
||||||
|
{"/flash.bin", NULL, cgiGetFlash},
|
||||||
{NULL, NULL, NULL},
|
{NULL, NULL, NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const UrlData cpioUrlData={"*", NULL, cgiSendFile};
|
||||||
|
|
||||||
static struct espconn conn;
|
static struct espconn conn;
|
||||||
static esp_tcp tcp;
|
static esp_tcp tcp;
|
||||||
|
|
||||||
static int recLen;
|
static int recLen;
|
||||||
static char recBuff[MAX_HEAD_LEN];
|
static char recBuff[MAX_HEAD_LEN];
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char *url;
|
|
||||||
char *getArgs;
|
|
||||||
const UrlData *effUrl;
|
|
||||||
} GetData;
|
|
||||||
|
|
||||||
static GetData getData;
|
static GetData getData;
|
||||||
|
|
||||||
//Find a specific arg in a string of get- or post-data.
|
//Find a specific arg in a string of get- or post-data.
|
||||||
@@ -70,18 +83,35 @@ static char *ICACHE_FLASH_ATTR httpdFindArg(char *line, char *arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//ToDo: Move cgi functions to somewhere else
|
//ToDo: Move cgi functions to somewhere else
|
||||||
int cgiSet(struct espconn *conn) {
|
int ICACHE_FLASH_ATTR cgiSet(struct espconn *conn, GetData *getData) {
|
||||||
char *on;
|
char *on;
|
||||||
static const char okStr[]="<html><head><title>OK</title></head><body><p>OK</p></body></html>";
|
static const char okStr[]="<html><head><title>OK</title></head><body><p>OK</p></body></html>";
|
||||||
on=httpdFindArg(getData.getArgs, "led");
|
on=httpdFindArg(getData->getArgs, "led");
|
||||||
os_printf("cgiSet: on=%s\n", on?on:"not found");
|
os_printf("cgiSet: on=%s\n", on?on:"not found");
|
||||||
if (on!=NULL) ioLed(atoi(on));
|
if (on!=NULL) ioLed(atoi(on));
|
||||||
espconn_sent(conn, (uint8 *)okStr, os_strlen(okStr));
|
espconn_sent(conn, (uint8 *)okStr, os_strlen(okStr));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ICACHE_FLASH_ATTR cgiGetFlash(struct espconn *conn, GetData *getData) {
|
||||||
|
static char *p=(char *)0x40200000;
|
||||||
|
static int t=0;
|
||||||
|
espconn_sent(conn, (uint8 *)p, 1024);
|
||||||
|
p+=1024;
|
||||||
|
t++;
|
||||||
|
if (t<1024) return 0;
|
||||||
|
t=0;
|
||||||
|
p=(char*)0x40200000;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ICACHE_FLASH_ATTR cgiSendFile(struct espconn *conn, GetData *getData) {
|
||||||
|
int len;
|
||||||
|
char buff[1024];
|
||||||
|
len=espFsRead(getData->file, buff, 1024);
|
||||||
|
espconn_sent(conn, (uint8 *)buff, len);
|
||||||
|
return (len==0);
|
||||||
|
}
|
||||||
|
|
||||||
static const char *httpOkHeader="HTTP/1.0 200 OK\r\nServer: esp8266-thingie/0.1\r\nContent-Type: text/html\r\n\r\n";
|
static const char *httpOkHeader="HTTP/1.0 200 OK\r\nServer: esp8266-thingie/0.1\r\nContent-Type: text/html\r\n\r\n";
|
||||||
static const char *httpNotFoundHeader="HTTP/1.0 404 Not Found\r\nServer: esp8266-thingie/0.1\r\n\r\nNot Found.\r\n";
|
static const char *httpNotFoundHeader="HTTP/1.0 404 Not Found\r\nServer: esp8266-thingie/0.1\r\n\r\nNot Found.\r\n";
|
||||||
@@ -94,7 +124,7 @@ static void ICACHE_FLASH_ATTR httpdSentCb(void *arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (getData.effUrl->cgiCb!=NULL) {
|
if (getData.effUrl->cgiCb!=NULL) {
|
||||||
if (getData.effUrl->cgiCb(conn)) getData.effUrl=NULL;
|
if (getData.effUrl->cgiCb(conn, &getData)) getData.effUrl=NULL;
|
||||||
} else {
|
} else {
|
||||||
espconn_sent(conn, (uint8 *)getData.effUrl->fixedResp, os_strlen(getData.effUrl->fixedResp));
|
espconn_sent(conn, (uint8 *)getData.effUrl->fixedResp, os_strlen(getData.effUrl->fixedResp));
|
||||||
getData.effUrl=NULL;
|
getData.effUrl=NULL;
|
||||||
@@ -103,6 +133,8 @@ static void ICACHE_FLASH_ATTR httpdSentCb(void *arg) {
|
|||||||
|
|
||||||
static void ICACHE_FLASH_ATTR httpdSendResp(struct espconn *conn) {
|
static void ICACHE_FLASH_ATTR httpdSendResp(struct espconn *conn) {
|
||||||
int i=0;
|
int i=0;
|
||||||
|
EspFsFile *fdat;
|
||||||
|
//See if the url is somewhere in our internal url table.
|
||||||
while (urls[i].url!=NULL) {
|
while (urls[i].url!=NULL) {
|
||||||
if (os_strcmp(urls[i].url, getData.url)==0) {
|
if (os_strcmp(urls[i].url, getData.url)==0) {
|
||||||
getData.effUrl=&urls[i];
|
getData.effUrl=&urls[i];
|
||||||
@@ -112,6 +144,16 @@ static void ICACHE_FLASH_ATTR httpdSendResp(struct espconn *conn) {
|
|||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
//Nope. See if it's in the cpio archive
|
||||||
|
fdat=espFsOpen(getData.url);
|
||||||
|
if (fdat!=NULL) {
|
||||||
|
//Found
|
||||||
|
getData.file=fdat;
|
||||||
|
getData.effUrl=&cpioUrlData;
|
||||||
|
espconn_sent(conn, (uint8 *)httpOkHeader, os_strlen(httpOkHeader));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Can't find :/
|
//Can't find :/
|
||||||
espconn_sent(conn, (uint8 *)httpNotFoundHeader, os_strlen(httpNotFoundHeader));
|
espconn_sent(conn, (uint8 *)httpNotFoundHeader, os_strlen(httpNotFoundHeader));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user