Implement a quota for image data

This commit is contained in:
Kovid Goyal 2017-10-09 11:31:57 +05:30
parent b4b6968e07
commit f396fe59ca
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 71 additions and 20 deletions

View File

@ -280,6 +280,9 @@ x=1, y=1 is the top left cell).
| `y` or `Y` | Delete all images that intersect the specified row, specified using the `y` key.
| `z` or `Z` | Delete all images that have the specified z-index, specified using the `z` key.
|===
Some examples:
```
@ -288,7 +291,15 @@ Some examples:
<ESC>_Ga=Z,z=-1<ESC>\ # delete the images with z-index -1, also freeing up image data
<ESC>_Ga=P,x=3,y=4<ESC>\ # delete all images that intersect the cell at (3, 4)
```
|===
=== Image persistence and storage quotas
In order to avoid *Denial-of-Service* attacks, terminal emulators should have a
maximum storage quota for image data. It should allow at least a few full
screen images. For example the quota in kitty is 320MB per buffer. When adding
a new image, if the total size exceeds the quota, the terminal emulator should
delete older images to make space for the new one.
== Control data reference

View File

@ -18,6 +18,8 @@
#include <png.h>
#include <structmember.h>
#define STORAGE_LIMIT (320 * (1024 * 1024))
#define REPORT_ERROR(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); }
static bool send_to_gpu = true;
@ -59,17 +61,19 @@ free_texture_func free_texture = NULL;
send_image_to_gpu_func send_image_to_gpu = NULL;
static inline void
free_image(Image *img) {
free_image(GraphicsManager *self, Image *img) {
if (img->texture_id) free_texture(&img->texture_id);
free_refs_data(img);
free_load_data(&(img->load_data));
self->used_storage -= img->used_storage;
}
static void
dealloc(GraphicsManager* self) {
size_t i;
if (self->images) {
for (i = 0; i < self->image_count; i++) free_image(self->images + i);
for (i = 0; i < self->image_count; i++) free_image(self, self->images + i);
free(self->images);
}
free(self->render_data);
@ -114,9 +118,51 @@ img_by_client_id(GraphicsManager *self, uint32_t id) {
return NULL;
}
static inline void
remove_image(GraphicsManager *self, size_t idx) {
free_image(self, self->images + idx);
remove_from_array(self->images, sizeof(Image), idx, self->image_count--);
self->layers_dirty = true;
}
static inline void
remove_images(GraphicsManager *self, bool(*predicate)(Image*), Image* skip_image) {
for (size_t i = self->image_count; i-- > 0;) {
Image *img = self->images + i;
if (img != skip_image && predicate(img)) {
remove_image(self, i);
}
}
}
// Loading image data {{{
static bool
trim_predicate(Image *img) {
return !img->data_loaded || !img->refcnt;
}
static int
oldest_last(const void* a, const void *b) {
double ans = ((Image*)(b))->atime - ((Image*)(a))->atime;
return ans < 0 ? -1 : (ans == 0 ? 0 : 1);
}
static inline void
apply_storage_quota(GraphicsManager *self, size_t storage_limit, Image *currently_added_image) {
// First remove unreferenced images, even if they have an id
remove_images(self, trim_predicate, currently_added_image);
if (self->used_storage < storage_limit) return;
qsort(self->images, self->image_count, sizeof(Image), oldest_last);
while (self->used_storage > storage_limit && self->image_count > 0) {
remove_image(self, self->image_count - 1);
}
if (!self->image_count) self->used_storage = 0; // sanity check
}
static char add_response[512] = {0};
static bool has_add_respose = false;
@ -315,17 +361,6 @@ find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) {
return self->images + self->image_count++;
}
static inline void
remove_images(GraphicsManager *self, bool(*predicate)(Image*)) {
for (size_t i = self->image_count; i-- > 0;) {
if (predicate(self->images + i)) {
free_image(self->images + i);
remove_from_array(self->images, sizeof(Image), i, self->image_count--);
self->layers_dirty = true;
}
}
}
static Image*
handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, bool *is_dirty, uint32_t iid) {
@ -343,7 +378,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
self->last_init_graphics_command.id = iid;
self->loading_image = 0;
if (g->data_width > 10000 || g->data_height > 10000) ABRT(EINVAL, "Image too large");
remove_images(self, add_trim_predicate);
remove_images(self, add_trim_predicate, NULL);
img = find_or_create_image(self, iid, &existing);
if (existing) {
free_load_data(&img->load_data);
@ -355,6 +390,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
img->internal_id = internal_id_counter++;
img->client_id = iid;
}
img->atime = monotonic(); img->used_storage = 0;
img->width = g->data_width; img->height = g->data_height;
switch(fmt) {
case PNG:
@ -480,6 +516,8 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
if (LIKELY(img->data_loaded && send_to_gpu)) {
send_image_to_gpu(&img->texture_id, img->load_data.data, img->width, img->height, img->load_data.is_opaque, img->load_data.is_4byte_aligned);
free_load_data(&img->load_data);
self->used_storage += required_sz;
img->used_storage = required_sz;
}
return img;
#undef MAX_DATA_SZ
@ -537,6 +575,7 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
}
}
if (ref == NULL) ref = img->refs + img->refcnt++;
img->atime = monotonic();
ref->src_x = g->x_offset; ref->src_y = g->y_offset; ref->src_width = g->width ? g->width : img->width; ref->src_height = g->height ? g->height : img->height;
ref->src_width = MIN(ref->src_width, img->width - (img->width > ref->src_x ? ref->src_x : img->width));
ref->src_height = MIN(ref->src_height, img->height - (img->height > ref->src_y ? ref->src_y : img->height));
@ -644,10 +683,7 @@ filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*fi
remove_from_array(img->refs, sizeof(ImageRef), j, img->refcnt--);
}
}
if (img->refcnt == 0 && (free_images || img->client_id == 0)) {
free_image(img);
remove_from_array(self->images, sizeof(Image), i, self->image_count--);
}
if (img->refcnt == 0 && (free_images || img->client_id == 0)) remove_image(self, i);
}
self->layers_dirty = true;
}
@ -798,7 +834,8 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint
image = handle_add_command(self, g, payload, is_dirty, iid);
ret = create_add_response(self, image != NULL, g->action == 'q' ? q_iid: self->last_init_graphics_command.id);
if (self->last_init_graphics_command.action == 'T' && image && image->data_loaded) handle_put_command(self, &self->last_init_graphics_command, c, is_dirty, image);
if (g->action == 'q') remove_images(self, add_trim_predicate);
if (g->action == 'q') remove_images(self, add_trim_predicate, NULL);
if (self->used_storage > STORAGE_LIMIT) apply_storage_quota(self, STORAGE_LIMIT, image);
break;
case 'p':
if (!g->id) {

View File

@ -50,6 +50,8 @@ typedef struct {
ImageRef *refs;
size_t refcnt, refcap;
double atime;
size_t used_storage;
} Image;
typedef struct {
@ -71,6 +73,7 @@ typedef struct {
bool layers_dirty;
size_t num_of_negative_refs, num_of_positive_refs;
unsigned int last_scrolled_by;
size_t used_storage;
} GraphicsManager;
PyTypeObject GraphicsManager_Type;