1 /*
2  * Copyright 2013 MongoDB Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
18 #undef MONGOC_LOG_DOMAIN
19 #define MONGOC_LOG_DOMAIN "gridfs"
20 
21 #include "mongoc-bulk-operation.h"
22 #include "mongoc-client-private.h"
23 #include "mongoc-collection.h"
24 #include "mongoc-collection-private.h"
25 #include "mongoc-error.h"
26 #include "mongoc-index.h"
27 #include "mongoc-gridfs.h"
28 #include "mongoc-gridfs-private.h"
29 #include "mongoc-gridfs-file.h"
30 #include "mongoc-gridfs-file-private.h"
31 #include "mongoc-gridfs-file-list.h"
32 #include "mongoc-gridfs-file-list-private.h"
33 #include "mongoc-client.h"
34 #include "mongoc-trace-private.h"
35 #include "mongoc-cursor-private.h"
36 #include "mongoc-util-private.h"
37 
38 #define MONGOC_GRIDFS_STREAM_CHUNK 4096
39 
40 
41 /**
42  * _mongoc_gridfs_ensure_index:
43  *
44  * ensure gridfs indexes
45  *
46  * Ensure fast searches for chunks via [ files_id, n ]
47  * Ensure fast searches for files via [ filename ]
48  */
49 static bool
_mongoc_gridfs_ensure_index(mongoc_gridfs_t * gridfs,bson_error_t * error)50 _mongoc_gridfs_ensure_index (mongoc_gridfs_t *gridfs, bson_error_t *error)
51 {
52    bson_t keys;
53    bson_t opts;
54    bool r;
55 
56    ENTRY;
57 
58    bson_init (&opts);
59    BSON_APPEND_BOOL (&opts, "unique", true);
60 
61    bson_init (&keys);
62 
63    BSON_APPEND_INT32 (&keys, "files_id", 1);
64    BSON_APPEND_INT32 (&keys, "n", 1);
65 
66    r = _mongoc_collection_create_index_if_not_exists (
67       gridfs->chunks, &keys, &opts, error);
68 
69    bson_destroy (&opts);
70    bson_destroy (&keys);
71 
72    if (!r) {
73       RETURN (r);
74    }
75 
76    bson_init (&keys);
77 
78    BSON_APPEND_INT32 (&keys, "filename", 1);
79    BSON_APPEND_INT32 (&keys, "uploadDate", 1);
80 
81    r = _mongoc_collection_create_index_if_not_exists (
82       gridfs->files, &keys, NULL, error);
83 
84    bson_destroy (&keys);
85 
86    if (!r) {
87       RETURN (r);
88    }
89 
90    RETURN (1);
91 }
92 
93 
94 mongoc_gridfs_t *
_mongoc_gridfs_new(mongoc_client_t * client,const char * db,const char * prefix,bson_error_t * error)95 _mongoc_gridfs_new (mongoc_client_t *client,
96                     const char *db,
97                     const char *prefix,
98                     bson_error_t *error)
99 {
100    mongoc_gridfs_t *gridfs;
101    char buf[128];
102    bool r;
103    uint32_t prefix_len;
104 
105    ENTRY;
106 
107    BSON_ASSERT (client);
108    BSON_ASSERT (db);
109 
110    if (!prefix) {
111       prefix = "fs";
112    }
113 
114    /* make sure prefix is short enough to bucket the chunks and files
115     * collections
116     */
117    prefix_len = (uint32_t) strlen (prefix);
118    BSON_ASSERT (prefix_len + sizeof (".chunks") < sizeof (buf));
119 
120    gridfs = (mongoc_gridfs_t *) bson_malloc0 (sizeof *gridfs);
121 
122    gridfs->client = client;
123 
124    bson_snprintf (buf, sizeof (buf), "%s.chunks", prefix);
125    gridfs->chunks = mongoc_client_get_collection (client, db, buf);
126 
127    bson_snprintf (buf, sizeof (buf), "%s.files", prefix);
128    gridfs->files = mongoc_client_get_collection (client, db, buf);
129 
130    r = _mongoc_gridfs_ensure_index (gridfs, error);
131 
132    if (!r) {
133       mongoc_gridfs_destroy (gridfs);
134       RETURN (NULL);
135    }
136 
137    RETURN (gridfs);
138 }
139 
140 
141 bool
mongoc_gridfs_drop(mongoc_gridfs_t * gridfs,bson_error_t * error)142 mongoc_gridfs_drop (mongoc_gridfs_t *gridfs, bson_error_t *error)
143 {
144    bool r;
145 
146    ENTRY;
147 
148    r = mongoc_collection_drop (gridfs->files, error);
149    if (!r) {
150       RETURN (0);
151    }
152 
153    r = mongoc_collection_drop (gridfs->chunks, error);
154    if (!r) {
155       RETURN (0);
156    }
157 
158    RETURN (1);
159 }
160 
161 
162 void
mongoc_gridfs_destroy(mongoc_gridfs_t * gridfs)163 mongoc_gridfs_destroy (mongoc_gridfs_t *gridfs)
164 {
165    ENTRY;
166 
167    if (!gridfs) {
168       EXIT;
169    }
170 
171    mongoc_collection_destroy (gridfs->files);
172    mongoc_collection_destroy (gridfs->chunks);
173 
174    bson_free (gridfs);
175 
176    EXIT;
177 }
178 
179 
180 /** find all matching gridfs files */
181 mongoc_gridfs_file_list_t *
mongoc_gridfs_find(mongoc_gridfs_t * gridfs,const bson_t * query)182 mongoc_gridfs_find (mongoc_gridfs_t *gridfs, const bson_t *query)
183 {
184    return _mongoc_gridfs_file_list_new (gridfs, query, 0);
185 }
186 
187 
188 /** find a single gridfs file */
189 mongoc_gridfs_file_t *
mongoc_gridfs_find_one(mongoc_gridfs_t * gridfs,const bson_t * query,bson_error_t * error)190 mongoc_gridfs_find_one (mongoc_gridfs_t *gridfs,
191                         const bson_t *query,
192                         bson_error_t *error)
193 {
194    mongoc_gridfs_file_list_t *list;
195    mongoc_gridfs_file_t *file;
196 
197    ENTRY;
198 
199    list = _mongoc_gridfs_file_list_new (gridfs, query, 1);
200 
201    file = mongoc_gridfs_file_list_next (list);
202    if (!mongoc_gridfs_file_list_error (list, error) && error) {
203       /* no error, but an error out-pointer was provided - clear it */
204       memset (error, 0, sizeof (*error));
205    }
206 
207    mongoc_gridfs_file_list_destroy (list);
208 
209    RETURN (file);
210 }
211 
212 
213 /** find all matching gridfs files */
214 mongoc_gridfs_file_list_t *
mongoc_gridfs_find_with_opts(mongoc_gridfs_t * gridfs,const bson_t * filter,const bson_t * opts)215 mongoc_gridfs_find_with_opts (mongoc_gridfs_t *gridfs,
216                               const bson_t *filter,
217                               const bson_t *opts)
218 {
219    return _mongoc_gridfs_file_list_new_with_opts (gridfs, filter, opts);
220 }
221 
222 
223 /** find a single gridfs file */
224 mongoc_gridfs_file_t *
mongoc_gridfs_find_one_with_opts(mongoc_gridfs_t * gridfs,const bson_t * filter,const bson_t * opts,bson_error_t * error)225 mongoc_gridfs_find_one_with_opts (mongoc_gridfs_t *gridfs,
226                                   const bson_t *filter,
227                                   const bson_t *opts,
228                                   bson_error_t *error)
229 {
230    mongoc_gridfs_file_list_t *list;
231    mongoc_gridfs_file_t *file;
232    bson_t new_opts;
233 
234    ENTRY;
235 
236    bson_init (&new_opts);
237 
238    if (opts) {
239       bson_copy_to_excluding_noinit (opts, &new_opts, "limit", (char *) NULL);
240    }
241 
242    BSON_APPEND_INT32 (&new_opts, "limit", 1);
243 
244    list = _mongoc_gridfs_file_list_new_with_opts (gridfs, filter, &new_opts);
245    file = mongoc_gridfs_file_list_next (list);
246 
247    if (!mongoc_gridfs_file_list_error (list, error) && error) {
248       /* no error, but an error out-pointer was provided - clear it */
249       memset (error, 0, sizeof (*error));
250    }
251 
252    mongoc_gridfs_file_list_destroy (list);
253    bson_destroy (&new_opts);
254 
255    RETURN (file);
256 }
257 
258 
259 /** find a single gridfs file by filename */
260 mongoc_gridfs_file_t *
mongoc_gridfs_find_one_by_filename(mongoc_gridfs_t * gridfs,const char * filename,bson_error_t * error)261 mongoc_gridfs_find_one_by_filename (mongoc_gridfs_t *gridfs,
262                                     const char *filename,
263                                     bson_error_t *error)
264 {
265    mongoc_gridfs_file_t *file;
266 
267    bson_t filter;
268 
269    bson_init (&filter);
270 
271    bson_append_utf8 (&filter, "filename", -1, filename, -1);
272 
273    file = mongoc_gridfs_find_one_with_opts (gridfs, &filter, NULL, error);
274 
275    bson_destroy (&filter);
276 
277    return file;
278 }
279 
280 
281 /** create a gridfs file from a stream
282  *
283  * The stream is fully consumed in creating the file
284  */
285 mongoc_gridfs_file_t *
mongoc_gridfs_create_file_from_stream(mongoc_gridfs_t * gridfs,mongoc_stream_t * stream,mongoc_gridfs_file_opt_t * opt)286 mongoc_gridfs_create_file_from_stream (mongoc_gridfs_t *gridfs,
287                                        mongoc_stream_t *stream,
288                                        mongoc_gridfs_file_opt_t *opt)
289 {
290    mongoc_gridfs_file_t *file;
291    ssize_t r;
292    uint8_t buf[MONGOC_GRIDFS_STREAM_CHUNK];
293    mongoc_iovec_t iov;
294    int timeout;
295 
296    ENTRY;
297 
298    BSON_ASSERT (gridfs);
299    BSON_ASSERT (stream);
300 
301    iov.iov_base = (void *) buf;
302    iov.iov_len = 0;
303 
304    file = _mongoc_gridfs_file_new (gridfs, opt);
305    timeout = gridfs->client->cluster.sockettimeoutms;
306 
307    for (;;) {
308       r = mongoc_stream_read (
309          stream, iov.iov_base, MONGOC_GRIDFS_STREAM_CHUNK, 0, timeout);
310 
311       if (r > 0) {
312          iov.iov_len = r;
313          if (mongoc_gridfs_file_writev (file, &iov, 1, timeout) < 0) {
314             MONGOC_ERROR ("%s", file->error.message);
315             mongoc_gridfs_file_destroy (file);
316             RETURN (NULL);
317          }
318       } else if (r == 0) {
319          break;
320       } else {
321          MONGOC_ERROR ("Error reading from GridFS file source stream");
322          mongoc_gridfs_file_destroy (file);
323          RETURN (NULL);
324       }
325    }
326 
327    mongoc_stream_failed (stream);
328 
329    if (-1 == mongoc_gridfs_file_seek (file, 0, SEEK_SET)) {
330       MONGOC_ERROR ("%s", file->error.message);
331       mongoc_gridfs_file_destroy (file);
332       RETURN (NULL);
333    }
334 
335    RETURN (file);
336 }
337 
338 
339 /** create an empty gridfs file */
340 mongoc_gridfs_file_t *
mongoc_gridfs_create_file(mongoc_gridfs_t * gridfs,mongoc_gridfs_file_opt_t * opt)341 mongoc_gridfs_create_file (mongoc_gridfs_t *gridfs,
342                            mongoc_gridfs_file_opt_t *opt)
343 {
344    mongoc_gridfs_file_t *file;
345 
346    ENTRY;
347 
348    BSON_ASSERT (gridfs);
349 
350    file = _mongoc_gridfs_file_new (gridfs, opt);
351 
352    RETURN (file);
353 }
354 
355 /** accessor functions for collections */
356 mongoc_collection_t *
mongoc_gridfs_get_files(mongoc_gridfs_t * gridfs)357 mongoc_gridfs_get_files (mongoc_gridfs_t *gridfs)
358 {
359    BSON_ASSERT (gridfs);
360 
361    return gridfs->files;
362 }
363 
364 mongoc_collection_t *
mongoc_gridfs_get_chunks(mongoc_gridfs_t * gridfs)365 mongoc_gridfs_get_chunks (mongoc_gridfs_t *gridfs)
366 {
367    BSON_ASSERT (gridfs);
368 
369    return gridfs->chunks;
370 }
371 
372 
373 bool
mongoc_gridfs_remove_by_filename(mongoc_gridfs_t * gridfs,const char * filename,bson_error_t * error)374 mongoc_gridfs_remove_by_filename (mongoc_gridfs_t *gridfs,
375                                   const char *filename,
376                                   bson_error_t *error)
377 {
378    mongoc_bulk_operation_t *bulk_files = NULL;
379    mongoc_bulk_operation_t *bulk_chunks = NULL;
380    mongoc_cursor_t *cursor = NULL;
381    bson_error_t files_error;
382    bson_error_t chunks_error;
383    const bson_t *doc;
384    const char *key;
385    char keybuf[16];
386    int count = 0;
387    bool chunks_ret;
388    bool files_ret;
389    bool ret = false;
390    bson_iter_t iter;
391    bson_t *files_q = NULL;
392    bson_t *chunks_q = NULL;
393    bson_t find_filter = BSON_INITIALIZER;
394    bson_t find_opts = BSON_INITIALIZER;
395    bson_t find_opts_project;
396    bson_t ar = BSON_INITIALIZER;
397    bson_t opts = BSON_INITIALIZER;
398 
399    BSON_ASSERT (gridfs);
400 
401    if (!filename) {
402       bson_set_error (error,
403                       MONGOC_ERROR_GRIDFS,
404                       MONGOC_ERROR_GRIDFS_INVALID_FILENAME,
405                       "A non-NULL filename must be specified.");
406       return false;
407    }
408 
409    /*
410     * Find all files matching this filename. Hopefully just one, but not
411     * strictly required!
412     */
413 
414    BSON_APPEND_UTF8 (&find_filter, "filename", filename);
415    BSON_APPEND_DOCUMENT_BEGIN (&find_opts, "projection", &find_opts_project);
416    BSON_APPEND_INT32 (&find_opts_project, "_id", 1);
417    bson_append_document_end (&find_opts, &find_opts_project);
418 
419    cursor = _mongoc_cursor_find_new (gridfs->client,
420                                      gridfs->files->ns,
421                                      &find_filter,
422                                      &find_opts,
423                                      NULL /* user_prefs */,
424                                      NULL /* default_prefs */,
425                                      NULL /* read_concern */);
426 
427    BSON_ASSERT (cursor);
428 
429    while (mongoc_cursor_next (cursor, &doc)) {
430       if (bson_iter_init_find (&iter, doc, "_id")) {
431          const bson_value_t *value = bson_iter_value (&iter);
432 
433          bson_uint32_to_string (count, &key, keybuf, sizeof keybuf);
434          BSON_APPEND_VALUE (&ar, key, value);
435       }
436    }
437 
438    if (mongoc_cursor_error (cursor, error)) {
439       goto failure;
440    }
441 
442    bson_append_bool (&opts, "ordered", 7, false);
443    bulk_files =
444       mongoc_collection_create_bulk_operation_with_opts (gridfs->files, &opts);
445    bulk_chunks =
446       mongoc_collection_create_bulk_operation_with_opts (gridfs->chunks, &opts);
447 
448    bson_destroy (&opts);
449 
450    files_q = BCON_NEW ("_id", "{", "$in", BCON_ARRAY (&ar), "}");
451    chunks_q = BCON_NEW ("files_id", "{", "$in", BCON_ARRAY (&ar), "}");
452 
453    mongoc_bulk_operation_remove (bulk_files, files_q);
454    mongoc_bulk_operation_remove (bulk_chunks, chunks_q);
455 
456    files_ret = mongoc_bulk_operation_execute (bulk_files, NULL, &files_error);
457    chunks_ret =
458       mongoc_bulk_operation_execute (bulk_chunks, NULL, &chunks_error);
459 
460    if (error) {
461       if (!files_ret) {
462          memcpy (error, &files_error, sizeof *error);
463       } else if (!chunks_ret) {
464          memcpy (error, &chunks_error, sizeof *error);
465       }
466    }
467 
468    ret = (files_ret && chunks_ret);
469 
470 failure:
471    if (cursor) {
472       mongoc_cursor_destroy (cursor);
473    }
474    if (bulk_files) {
475       mongoc_bulk_operation_destroy (bulk_files);
476    }
477    if (bulk_chunks) {
478       mongoc_bulk_operation_destroy (bulk_chunks);
479    }
480    bson_destroy (&find_filter);
481    bson_destroy (&find_opts);
482    bson_destroy (&ar);
483    if (files_q) {
484       bson_destroy (files_q);
485    }
486    if (chunks_q) {
487       bson_destroy (chunks_q);
488    }
489 
490    return ret;
491 }
492