mirror of
https://github.com/esphome/esphome.git
synced 2026-09-17 18:18:43 +00:00
[core] Deprecate status_set_error(const char*) and require LogString to prevent dangling pointers
This commit is contained in:
@@ -87,7 +87,7 @@ void AbsoluteHumidityComponent::loop() {
|
||||
break;
|
||||
default:
|
||||
this->publish_state(NAN);
|
||||
this->status_set_error("Invalid saturation vapor pressure equation selection!");
|
||||
this->status_set_error(LOG_STR("Invalid saturation vapor pressure equation selection!"));
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
|
||||
|
||||
@@ -83,7 +83,7 @@ void AHT10Component::setup() {
|
||||
void AHT10Component::restart_read_() {
|
||||
if (this->read_count_ == AHT10_ATTEMPTS) {
|
||||
this->read_count_ = 0;
|
||||
this->status_set_error("Reading timed out");
|
||||
this->status_set_error(LOG_STR("Reading timed out"));
|
||||
return;
|
||||
}
|
||||
this->read_count_++;
|
||||
|
||||
@@ -8,7 +8,7 @@ Camera *Camera::global_camera = nullptr;
|
||||
|
||||
Camera::Camera() {
|
||||
if (global_camera != nullptr) {
|
||||
this->status_set_error("Multiple cameras are configured, but only one is supported.");
|
||||
this->status_set_error(LOG_STR("Multiple cameras are configured, but only one is supported."));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,13 +19,14 @@ void CST816Touchscreen::continue_setup_() {
|
||||
case CST816T_CHIP_ID:
|
||||
break;
|
||||
default:
|
||||
this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str());
|
||||
ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_);
|
||||
this->status_set_error(LOG_STR("Unknown chip ID"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
|
||||
} else if (!this->skip_probe_) {
|
||||
this->status_set_error("Failed to read chip id");
|
||||
this->status_set_error(LOG_STR("Failed to read chip id"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
hasher.add(this->firmware_data_, this->firmware_size_);
|
||||
hasher.calculate();
|
||||
if (!hasher.equals_bytes(this->firmware_sha256_.data())) {
|
||||
this->status_set_error("SHA256 verification failed");
|
||||
this->status_set_error(LOG_STR("SHA256 verification failed"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error("Failed to begin OTA");
|
||||
this->status_set_error(LOG_STR("Failed to begin OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
@@ -121,7 +121,7 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
|
||||
esp_hosted_slave_ota_end(); // NOLINT
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error("Failed to write OTA data");
|
||||
this->status_set_error(LOG_STR("Failed to write OTA data"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
@@ -134,7 +134,7 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error("Failed to end OTA");
|
||||
this->status_set_error(LOG_STR("Failed to end OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
@@ -144,7 +144,7 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error("Failed to activate OTA");
|
||||
this->status_set_error(LOG_STR("Failed to activate OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,20 +36,20 @@ void GDK101Component::setup() {
|
||||
uint8_t data[2];
|
||||
// first, reset the sensor
|
||||
if (!this->reset_sensor_(data)) {
|
||||
this->status_set_error("Reset failed!");
|
||||
this->status_set_error(LOG_STR("Reset failed!"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// sensor should acknowledge success of the reset procedure
|
||||
if (data[0] != 1) {
|
||||
this->status_set_error("Reset not acknowledged!");
|
||||
this->status_set_error(LOG_STR("Reset not acknowledged!"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(10);
|
||||
// read firmware version
|
||||
if (!this->read_fw_version_(data)) {
|
||||
this->status_set_error("Failed to read firmware version");
|
||||
this->status_set_error(LOG_STR("Failed to read firmware version"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ void HttpRequestUpdate::setup() {
|
||||
this->publish_state();
|
||||
} else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
|
||||
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||
this->status_set_error("Failed to install firmware");
|
||||
this->status_set_error(LOG_STR("Failed to install firmware"));
|
||||
this->publish_state();
|
||||
}
|
||||
});
|
||||
@@ -49,18 +49,19 @@ void HttpRequestUpdate::update_task(void *params) {
|
||||
auto container = this_update->request_parent_->get(this_update->source_url_);
|
||||
|
||||
if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
|
||||
std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str());
|
||||
ESP_LOGE(TAG, "Failed to fetch manifest from %s", this_update->source_url_.c_str());
|
||||
// Defer to main loop to avoid race condition on component_state_ read-modify-write
|
||||
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
|
||||
this_update->defer([this_update]() { this_update->status_set_error(LOG_STR("Failed to fetch manifest")); });
|
||||
UPDATE_RETURN;
|
||||
}
|
||||
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
uint8_t *data = allocator.allocate(container->content_length);
|
||||
if (data == nullptr) {
|
||||
std::string msg = str_sprintf("Failed to allocate %zu bytes for manifest", container->content_length);
|
||||
ESP_LOGE(TAG, "Failed to allocate %zu bytes for manifest", container->content_length);
|
||||
// Defer to main loop to avoid race condition on component_state_ read-modify-write
|
||||
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
|
||||
this_update->defer(
|
||||
[this_update]() { this_update->status_set_error(LOG_STR("Failed to allocate memory for manifest")); });
|
||||
container->end();
|
||||
UPDATE_RETURN;
|
||||
}
|
||||
@@ -121,9 +122,9 @@ void HttpRequestUpdate::update_task(void *params) {
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str());
|
||||
ESP_LOGE(TAG, "Failed to parse JSON from %s", this_update->source_url_.c_str());
|
||||
// Defer to main loop to avoid race condition on component_state_ read-modify-write
|
||||
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
|
||||
this_update->defer([this_update]() { this_update->status_set_error(LOG_STR("Failed to parse manifest JSON")); });
|
||||
UPDATE_RETURN;
|
||||
}
|
||||
|
||||
|
||||
@@ -466,7 +466,7 @@ void LvglComponent::setup() {
|
||||
buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
|
||||
}
|
||||
if (buffer == nullptr) {
|
||||
this->status_set_error("Memory allocation failure");
|
||||
this->status_set_error(LOG_STR("Memory allocation failure"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -479,7 +479,7 @@ void LvglComponent::setup() {
|
||||
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
|
||||
this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes)); // NOLINT
|
||||
if (this->rotate_buf_ == nullptr) {
|
||||
this->status_set_error("Memory allocation failure");
|
||||
this->status_set_error(LOG_STR("Memory allocation failure"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -57,14 +57,14 @@ void MAX17043Component::setup() {
|
||||
|
||||
if (config_reg != MAX17043_CONFIG_POWER_UP_DEFAULT) {
|
||||
ESP_LOGE(TAG, "Device does not appear to be a MAX17043");
|
||||
this->status_set_error("unrecognised");
|
||||
this->status_set_error(LOG_STR("unrecognised"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// need to write back to config register to reset the sleep bit
|
||||
if (!this->write_byte_16(MAX17043_CONFIG, MAX17043_CONFIG_POWER_UP_DEFAULT)) {
|
||||
this->status_set_error("sleep reset failed");
|
||||
this->status_set_error(LOG_STR("sleep reset failed"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,19 +78,20 @@ void SourceSpeaker::loop() {
|
||||
} else {
|
||||
switch (err) {
|
||||
case ESP_ERR_NO_MEM:
|
||||
this->status_set_error("Failed to start mixer: not enough memory");
|
||||
this->status_set_error(LOG_STR("Failed to start mixer: not enough memory"));
|
||||
break;
|
||||
case ESP_ERR_NOT_SUPPORTED:
|
||||
this->status_set_error("Failed to start mixer: unsupported bits per sample");
|
||||
this->status_set_error(LOG_STR("Failed to start mixer: unsupported bits per sample"));
|
||||
break;
|
||||
case ESP_ERR_INVALID_ARG:
|
||||
this->status_set_error("Failed to start mixer: audio stream isn't compatible with the other audio stream.");
|
||||
this->status_set_error(
|
||||
LOG_STR("Failed to start mixer: audio stream isn't compatible with the other audio stream."));
|
||||
break;
|
||||
case ESP_ERR_INVALID_STATE:
|
||||
this->status_set_error("Failed to start mixer: mixer task failed to start");
|
||||
this->status_set_error(LOG_STR("Failed to start mixer: mixer task failed to start"));
|
||||
break;
|
||||
default:
|
||||
this->status_set_error("Failed to start mixer");
|
||||
this->status_set_error(LOG_STR("Failed to start mixer"));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -317,7 +318,7 @@ void MixerSpeaker::loop() {
|
||||
xEventGroupClearBits(this->event_group_, MixerEventGroupBits::STATE_STARTING);
|
||||
}
|
||||
if (event_group_bits & MixerEventGroupBits::ERR_ESP_NO_MEM) {
|
||||
this->status_set_error("Failed to allocate the mixer's internal buffer");
|
||||
this->status_set_error(LOG_STR("Failed to allocate the mixer's internal buffer"));
|
||||
xEventGroupClearBits(this->event_group_, MixerEventGroupBits::ERR_ESP_NO_MEM);
|
||||
}
|
||||
if (event_group_bits & MixerEventGroupBits::STATE_RUNNING) {
|
||||
|
||||
@@ -278,7 +278,7 @@ void NAU7802Sensor::loop() {
|
||||
this->set_calibration_failure_(true);
|
||||
this->state_ = CalibrationState::INACTIVE;
|
||||
ESP_LOGE(TAG, "Failed to calibrate sensor");
|
||||
this->status_set_error("Calibration Failed");
|
||||
this->status_set_error(LOG_STR("Calibration Failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ static void add(std::vector<uint8_t> &vec, const char *str) {
|
||||
void PacketTransport::setup() {
|
||||
this->name_ = App.get_name().c_str();
|
||||
if (strlen(this->name_) > 255) {
|
||||
this->status_set_error("Device name exceeds 255 chars");
|
||||
this->status_set_error(LOG_STR("Device name exceeds 255 chars"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -66,17 +66,17 @@ void ResamplerSpeaker::loop() {
|
||||
}
|
||||
|
||||
if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_NO_MEM) {
|
||||
this->status_set_error("Resampler task failed to allocate the internal buffers");
|
||||
this->status_set_error(LOG_STR("Resampler task failed to allocate the internal buffers"));
|
||||
xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_NO_MEM);
|
||||
this->state_ = speaker::STATE_STOPPING;
|
||||
}
|
||||
if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_NOT_SUPPORTED) {
|
||||
this->status_set_error("Cannot resample due to an unsupported audio stream");
|
||||
this->status_set_error(LOG_STR("Cannot resample due to an unsupported audio stream"));
|
||||
xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_NOT_SUPPORTED);
|
||||
this->state_ = speaker::STATE_STOPPING;
|
||||
}
|
||||
if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_FAIL) {
|
||||
this->status_set_error("Resampler task failed");
|
||||
this->status_set_error(LOG_STR("Resampler task failed"));
|
||||
xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_FAIL);
|
||||
this->state_ = speaker::STATE_STOPPING;
|
||||
}
|
||||
@@ -106,12 +106,12 @@ void ResamplerSpeaker::loop() {
|
||||
} else {
|
||||
switch (err) {
|
||||
case ESP_ERR_INVALID_STATE:
|
||||
this->status_set_error("Failed to start resampler: resampler task failed to start");
|
||||
this->status_set_error(LOG_STR("Failed to start resampler: resampler task failed to start"));
|
||||
break;
|
||||
case ESP_ERR_NO_MEM:
|
||||
this->status_set_error("Failed to start resampler: not enough memory for task stack");
|
||||
this->status_set_error(LOG_STR("Failed to start resampler: not enough memory for task stack"));
|
||||
default:
|
||||
this->status_set_error("Failed to start resampler");
|
||||
this->status_set_error(LOG_STR("Failed to start resampler"));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ void SHT4XComponent::start_heater_() {
|
||||
|
||||
ESP_LOGD(TAG, "Heater turning on");
|
||||
if (this->write(cmd, 1) != i2c::ERROR_OK) {
|
||||
this->status_set_error("Failed to turn on heater");
|
||||
this->status_set_error(LOG_STR("Failed to turn on heater"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ void UDPComponent::setup() {
|
||||
if (this->should_broadcast_) {
|
||||
this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (this->broadcast_socket_ == nullptr) {
|
||||
this->status_set_error("Could not create socket");
|
||||
this->status_set_error(LOG_STR("Could not create socket"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -41,14 +41,14 @@ void UDPComponent::setup() {
|
||||
if (this->should_listen_) {
|
||||
this->listen_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (this->listen_socket_ == nullptr) {
|
||||
this->status_set_error("Could not create socket");
|
||||
this->status_set_error(LOG_STR("Could not create socket"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
auto err = this->listen_socket_->setblocking(false);
|
||||
if (err < 0) {
|
||||
ESP_LOGE(TAG, "Unable to set nonblocking: errno %d", errno);
|
||||
this->status_set_error("Unable to set nonblocking");
|
||||
this->status_set_error(LOG_STR("Unable to set nonblocking"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ void UDPComponent::setup() {
|
||||
err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq));
|
||||
if (err < 0) {
|
||||
ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno);
|
||||
this->status_set_error("Failed to set IP_ADD_MEMBERSHIP");
|
||||
this->status_set_error(LOG_STR("Failed to set IP_ADD_MEMBERSHIP"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -82,7 +82,7 @@ void UDPComponent::setup() {
|
||||
err = this->listen_socket_->bind((struct sockaddr *) &server, sizeof(server));
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
|
||||
this->status_set_error("Unable to bind socket");
|
||||
this->status_set_error(LOG_STR("Unable to bind socket"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ void USBClient::setup() {
|
||||
auto err = usb_host_client_register(&config, &this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "client register failed: %s", esp_err_to_name(err));
|
||||
this->status_set_error("Client register failed");
|
||||
this->status_set_error(LOG_STR("Client register failed"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ void USBHost::setup() {
|
||||
usb_host_config_t config{};
|
||||
|
||||
if (usb_host_install(&config) != ESP_OK) {
|
||||
this->status_set_error("usb_host_install failed");
|
||||
this->status_set_error(LOG_STR("usb_host_install failed"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ static void fix_mps(const usb_ep_desc_t *ep) {
|
||||
void USBUartTypeCdcAcm::on_connected() {
|
||||
auto cdc_devs = this->parse_descriptors(this->device_handle_);
|
||||
if (cdc_devs.empty()) {
|
||||
this->status_set_error("No CDC-ACM device found");
|
||||
this->status_set_error(LOG_STR("No CDC-ACM device found"));
|
||||
this->disconnect();
|
||||
return;
|
||||
}
|
||||
@@ -341,7 +341,7 @@ void USBUartTypeCdcAcm::on_connected() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
|
||||
channel->cdc_dev_.bulk_interface_number);
|
||||
this->status_set_error("usb_host_interface_claim failed");
|
||||
this->status_set_error(LOG_STR("usb_host_interface_claim failed"));
|
||||
this->disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ void VoiceAssistant::loop() {
|
||||
case State::START_MICROPHONE: {
|
||||
ESP_LOGD(TAG, "Starting Microphone");
|
||||
if (!this->allocate_buffers_()) {
|
||||
this->status_set_error("Failed to allocate buffers");
|
||||
this->status_set_error(LOG_STR("Failed to allocate buffers"));
|
||||
return;
|
||||
}
|
||||
if (this->status_has_error()) {
|
||||
|
||||
@@ -67,7 +67,7 @@ void WakeOnLanButton::setup() {
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (this->broadcast_socket_ == nullptr) {
|
||||
this->status_set_error("Could not create socket");
|
||||
this->status_set_error(LOG_STR("Could not create socket"));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -330,6 +330,31 @@ void Component::status_set_error(const char *message) {
|
||||
component_error_messages->emplace_back(ComponentErrorMessage{this, message});
|
||||
}
|
||||
}
|
||||
void Component::status_set_error(const LogString *message) {
|
||||
if ((this->component_state_ & STATUS_LED_ERROR) != 0)
|
||||
return;
|
||||
this->component_state_ |= STATUS_LED_ERROR;
|
||||
App.app_state_ |= STATUS_LED_ERROR;
|
||||
ESP_LOGE(TAG, "%s set Error flag: %s", LOG_STR_ARG(this->get_component_log_str()),
|
||||
message ? LOG_STR_ARG(message) : LOG_STR_LITERAL("unspecified"));
|
||||
if (message != nullptr) {
|
||||
// Lazy allocate the error messages vector if needed
|
||||
if (!component_error_messages) {
|
||||
component_error_messages = std::make_unique<std::vector<ComponentErrorMessage>>();
|
||||
}
|
||||
// Store the LogString pointer directly (safe because LogString is always in flash/static memory)
|
||||
const char *msg_ptr = LOG_STR_ARG(message);
|
||||
// Check if this component already has an error message
|
||||
for (auto &entry : *component_error_messages) {
|
||||
if (entry.component == this) {
|
||||
entry.message = msg_ptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Add new error message
|
||||
component_error_messages->emplace_back(ComponentErrorMessage{this, msg_ptr});
|
||||
}
|
||||
}
|
||||
void Component::status_clear_warning() {
|
||||
if ((this->component_state_ & STATUS_LED_WARNING) == 0)
|
||||
return;
|
||||
|
||||
@@ -216,7 +216,10 @@ class Component {
|
||||
void status_set_warning(const char *message = nullptr);
|
||||
void status_set_warning(const LogString *message);
|
||||
|
||||
// Remove before 2026.12.0
|
||||
ESPDEPRECATED("Use status_set_error(LOG_STR(\"message\")) instead. Removed in 2026.12.0", "2025.6.0")
|
||||
void status_set_error(const char *message = nullptr);
|
||||
void status_set_error(const LogString *message);
|
||||
|
||||
void status_clear_warning();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user