Bug Summary

File:playlist.c
Location:line 103, column 26
Description:Array access (via field 'files') results in a null pointer dereference

Annotated Source Code

1/*
2 * Copyright (c) 2010, 2011, 2012 Ryan Flannery <ryan.flannery@gmail.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#include "playlist.h"
18
19int history_size = DEFAULT_HISTORY_SIZE100;
20
21void
22playlist_increase_capacity(playlist *p)
23{
24 meta_info **new_files;
25 size_t nbytes;
26
27 p->capacity += PLAYLIST_CHUNK_SIZE100;
28 nbytes = p->capacity * sizeof(meta_info*);
29 if ((new_files = realloc(p->files, nbytes)) == NULL((void *)0))
30 err(1, "%s: failed to realloc(3) files", __FUNCTION__);
31
32 p->files = new_files;
33}
34
35/*
36 * Allocate a new playlist and return a pointer to it. The resulting
37 * structure must be free(2)'d using playlist_free().
38 */
39playlist *
40playlist_new(void)
41{
42 playlist *p;
43
44 if ((p = malloc(sizeof(playlist))) == NULL((void *)0))
45 err(1, "playlist_new: failed to allocate playlist");
46
47 if ((p->files = calloc(PLAYLIST_CHUNK_SIZE100, sizeof(meta_info*))) == NULL((void *)0))
48 err(1, "playlist_new: failed to allocate files");
49
50 p->capacity = PLAYLIST_CHUNK_SIZE100;
51 p->filename = NULL((void *)0);
52 p->name = NULL((void *)0);
53 p->nfiles = 0;
54 p->history = playlist_history_new();
55 p->hist_present = -1;
56 p->needs_saving = false0;
57
58 return p;
59}
60
61/* Free all of the memory consumed by a given playlist. */
62void
63playlist_free(playlist *p)
64{
65 if (p->filename != NULL((void *)0)) free(p->filename);
66 if (p->name != NULL((void *)0)) free(p->name);
67 if (p->files != NULL((void *)0)) free(p->files);
68 playlist_history_free(p);
69 free(p);
70}
71
72/*
73 * Duplicate an existing playlist, returning a pointer to the newly allocated
74 * playlist. The filename and name of the new playlist must be specified.
75 */
76playlist *
77playlist_dup(const playlist *original, const char *filename,
78 const char *name)
79{
80 playlist *newplist;
81 int i;
82
83 /* create new playlist and copy simple members */
84 newplist = playlist_new();
85 newplist->nfiles = original->nfiles;
86 newplist->capacity = original->nfiles;
87
88 if (name != NULL((void *)0)) {
1
Assuming 'name' is equal to null
2
Taking false branch
89 if ((newplist->name = strdup(name)) == NULL((void *)0))
90 err(1, "playlist_dup: strdup name failed");
91 }
92 if (filename != NULL((void *)0)) {
3
Assuming 'filename' is equal to null
4
Taking false branch
93 if ((newplist->filename = strdup(filename)) == NULL((void *)0))
94 err(1, "playlist_dup: strdup filename failed");
95 }
96
97 /* copy all of the files */
98 newplist->files = calloc(original->nfiles, sizeof(meta_info*));
5
Value assigned to field 'files'
99 if (newplist->files == NULL((void *)0))
6
Assuming pointer value is null
7
Taking true branch
100 err(1, "playlist_dup: failed to allocate files");
101
102 for (i = 0; i < original->nfiles; i++)
8
Loop condition is true. Entering loop body
103 newplist->files[i] = original->files[i];
9
Array access (via field 'files') results in a null pointer dereference
104
105 return newplist;
106}
107
108/*
109 * Add files to a playlist at the index specified by start. Note that if
110 * start is the length of the files array the files are appended to the end.
111 */
112void
113playlist_files_add(playlist *p, meta_info **f, int start, int size, bool_Bool record)
114{
115 int i;
116
117 if (start < 0 || start > p->nfiles)
118 errx(1, "playlist_file_add: index %d out of range", start);
119
120 while (p->capacity <= p->nfiles + size)
121 playlist_increase_capacity(p);
122
123 /* push everything after start back size places */
124 for (i = p->nfiles + size; i > start; i--)
125 p->files[i] = p->files[i - size];
126
127 /* add the files */
128 for (i = 0; i < size; i++)
129 p->files[start + i] = f[i];
130
131 p->nfiles += size;
132
133 /* update the history for this playlist */
134 if (record) {
135 playlist_changeset *changes = changeset_create(CHANGE_ADD0, size,
136 f, start);
137 playlist_history_push(p, changes);
138 p->needs_saving = true1;
139 }
140}
141
142/* Append a file to the end of a playlist */
143void
144playlist_files_append(playlist *p, meta_info **f, int size, bool_Bool record)
145{
146 return playlist_files_add(p, f, p->nfiles, size, record);
147}
148
149/* Remove a file at a given index from a playlist. */
150void
151playlist_files_remove(playlist *p, int start, int size, bool_Bool record)
152{
153 playlist_changeset *changes;
154 int i;
155
156 if (start < 0 || start >= p->nfiles)
157 errx(1, "playlist_remove_file: index %d out of range", start);
158
159 if (record) {
160 changes = changeset_create(CHANGE_REMOVE1, size, &(p->files[start]), start);
161 playlist_history_push(p, changes);
162 p->needs_saving = true1;
163 }
164
165 for (i = start; i < p->nfiles; i++)
166 p->files[i] = p->files[i + size];
167
168 p->nfiles -= size;
169
170}
171
172/* Replaces the file at a given index in a playlist with a new file */
173void
174playlist_file_replace(playlist *p, int index, meta_info *newEntry)
175{
176 if (index < 0 || index >= p->nfiles)
177 errx(1, "playlist_file_replace: index %d out of range", index);
178
179 p->files[index] = newEntry;
180}
181
182/* Used with bsearch to find playlist entry by filename. */
183static int cmp_fn_mi(const void *ai, const void *bi)
184{
185 const char *a = (const char *) ai;
186 const meta_info **b2 = (const meta_info **) bi;
187 const meta_info *b = (const meta_info *) *b2;
188
189 return strcmp(a, b->filename);
190}
191
192/*
193 * Loads a playlist from the provided filename. The files within the playlist
194 * are compared against the given meta-information-database to see if they
195 * exist there. If they do, the corresponding entry in the playlist structure
196 * built is simply a pointer to the existing entry. Otherwise, that file's
197 * meta information is set to NULL and the file is copied (allocated) in the
198 * playlist structure.
199 *
200 * A newly allocated playlist is returned.
201 */
202playlist *
203playlist_load(const char *filename, meta_info **db, int ndb)
204{
205 meta_info *mi, **mit;
206 FILE *fin;
207 char *period;
208 char entry[PATH_MAX1024 + 1];
209
210 /* open file */
211 if ((fin = fopen(filename, "r")) == NULL((void *)0))
212 err(1, "playlist_load: failed to open playlist '%s'", filename);
213
214 /* create playlist and setup */
215 playlist *p = playlist_new();
216 p->filename = strdup(filename);
217 p->name = strdup(basename(p->filename));
218 if (p->filename == NULL((void *)0) || p->name == NULL((void *)0))
219 err(1, "playlist_load: failed to allocate info for playlist '%s'", filename);
220
221 /* hack to remove '.playlist' from name */
222 period = strrchr(p->name, '.');
223 *period = '\0';
224
225 /* read each line from the file and copy into playlist object */
226 while (fgets(entry, PATH_MAX1024, fin) != NULL((void *)0)) {
227 /* sanitize */
228 entry[strcspn(entry, "\n")] = '\0';
229
230 /* check if file exists in the meta info. db */
231 mit = bsearch(entry, db, ndb, sizeof(meta_info *), cmp_fn_mi);
232
233 if (mit != NULL((void *)0)) { /* file DOES exist in DB */
234 mi = *mit;
235 playlist_files_append(p, &mi, 1, false0);
236 } else { /* file does NOT exist in DB */
237 /* create empty meta-info object with just the file name */
238 mi = mi_new();
239 mi->filename = strdup(entry);
240 if (mi->filename == NULL((void *)0))
241 err(1, "playlist_load: failed to strdup filename");
242
243 /* add new record to the db and link it to the playlist */
244 playlist_files_append(p, &mi, 1, false0);
245 warnx("playlist \"%s\", file \"%s\" is NOT in media database (added for now)",
246 p->name, entry);
247 }
248 }
249
250 fclose(fin);
251 return p;
252}
253
254/*
255 * Save a playlist to file. The filename used is whatever is in the
256 * playlist.
257 */
258void
259playlist_save(const playlist *p)
260{
261 FILE *fout;
262 int i;
263
264 if ((fout = fopen(p->filename, "w")) == NULL((void *)0))
265 err(1, "playlist_save: failed to open playlist \"%s\"", p->filename);
266
267 /* write each song to file */
268 for (i = 0; i < p->nfiles; i++) {
269 if (fprintf(fout, "%s\n", p->files[i]->filename) == -1)
270 err(1, "playlist_save: failed to record playlist \"%s\"", p->filename);
271 }
272
273 fclose(fout);
274}
275
276/*
277 * Deletes playlist from disk and destroy's the object, free()'ing all
278 * memory.
279 */
280void
281playlist_delete(playlist *p)
282{
283 /* delete file if the playlist is stored in a file */
284 if (p->filename != NULL((void *)0) && unlink(p->filename) != 0)
285 err(1, "playlist_delete: failed to delete playlist \"%s\"", p->filename);
286
287 /* destroy/free() all memory */
288 playlist_free(p);
289}
290
291/*
292 * Filter a playlist. After a query string is setup using the meta_info
293 * function "mi_query_set(..)" function, this function can be used to
294 * filter out all of the entried of a playlist that match/do-not-match
295 * that query string.
296 *
297 * The results are in the new playlist that is returned.
298 * If no query is set with mi_query_init(), NULL is returned.
299 *
300 * The 'm' parameter controls if records matching should be returned
301 * (m = true) or if records not matching should be returned (m=false)
302 */
303playlist *
304playlist_filter(const playlist *p, bool_Bool m)
305{
306 playlist *results;
307 int i;
308
309 if (!mi_query_isset())
310 return NULL((void *)0);
311
312 results = playlist_new();
313 for (i = 0; i < p->nfiles; i++) {
314 if (mi_match(p->files[i])) {
315 if (m) playlist_files_append(results, &(p->files[i]), 1, false0);
316 } else {
317 if (!m) playlist_files_append(results, &(p->files[i]), 1, false0);
318 }
319 }
320
321 return results;
322}
323
324/*
325 * Builds an array of all files in the given directory with a '.playlist'
326 * extension, returning the number of such files found.
327 *
328 * Parameters:
329 * dirname C-string of directory containing playlist files
330 *
331 * filenames Pointer to an array of C-strings that will be allocated
332 * and built in this function. This array is where the
333 * filename of each playlist will be stored.
334 * Returns:
335 * The number of files with a '.playlist' extension that were found.
336 *
337 * Notes:
338 * All allocation of the filenames array is handled here. It is the
339 * responsibility of the caller to free()
340 * 1. each element of that array
341 * 2. the array itself.
342 */
343int
344retrieve_playlist_filenames(const char *dirname, char ***fnames)
345{
346 char *glob_pattern;
347 glob_t files;
348 int globbed;
349# if defined(__linux) || defined(__FreeBSD__) || defined(__MACH__)
350 size_t fcount;
351# else
352 int fcount;
353# endif
354
355 /* build the search pattern */
356 if (asprintf(&glob_pattern, "%s/*.playlist", dirname) == -1)
357 errx(1, "failed in building glob pattern");
358
359 /* get the files */
360 globbed = glob(glob_pattern, 0, NULL((void *)0), &files);
361 if (globbed != 0 && globbed != GLOB_NOMATCH(-3) && errno(*__errno()) != 0)
362 err(1, "failed to glob playlists directory");
363
364 /* allocate & copy each of the filenames found into the filenames array */
365 if ((*fnames = calloc(files.gl_pathc, sizeof(char*))) == NULL((void *)0))
366 err(1, "failed to allocate playlist filenames array");
367
368 for (fcount = 0; fcount < files.gl_pathc; fcount++) {
369 if (asprintf(&((*fnames)[fcount]), "%s", files.gl_pathv[fcount]) == -1)
370 errx(1, "failed to allocate filename for playlist");
371 }
372
373 /* cleanup */
374 globfree(&files);
375 free(glob_pattern);
376
377 return fcount;
378}
379
380playlist_changeset*
381changeset_create(short type, size_t size, meta_info **files, int loc)
382{
383 size_t i;
384
385 playlist_changeset *c;
386
387 if ((c = malloc(sizeof(playlist_changeset))) == NULL((void *)0))
388 err(1, "%s: malloc(3) failed", __FUNCTION__);
389
390 if ((c->files = calloc(size, sizeof(meta_info*))) == NULL((void *)0))
391 err(1, "%s: calloc(3) failed", __FUNCTION__);
392
393 c->type = type;
394 c->size = size;
395 c->location = loc;
396
397 for (i = 0; i < size; i++)
398 c->files[i] = files[i];
399
400 return c;
401}
402
403void
404changeset_free(playlist_changeset *c)
405{
406 free(c->files);
407 free(c);
408}
409
410playlist_changeset**
411playlist_history_new(void)
412{
413 playlist_changeset **h;
414 int i;
415
416 if ((h = calloc(history_size, sizeof(playlist_changeset*))) == NULL((void *)0))
417 err(1, "%s: calloc(3) failed", __FUNCTION__);
418
419 for (i = 0; i < history_size; i++)
420 h[i] = NULL((void *)0);
421
422 return h;
423}
424
425void
426playlist_history_free_future(playlist *p)
427{
428 int i;
429
430 for (i = p->hist_present + 1; i < history_size; i++) {
431 if (p->history[i] != NULL((void *)0)) {
432 changeset_free(p->history[i]);
433 p->history[i] = NULL((void *)0);
434 }
435 }
436}
437
438void
439playlist_history_free(playlist *p)
440{
441 p->hist_present = 0;
442 playlist_history_free_future(p);
443 free(p->history);
444}
445
446void
447playlist_history_push(playlist *p, playlist_changeset *c)
448{
449 if (p->hist_present < history_size - 1)
450 playlist_history_free_future(p);
451 else {
452 int i;
453 for (i = 0; i < history_size - 1; i++)
454 p->history[i] = p->history[i + 1];
455
456 p->hist_present--;
457 }
458
459 p->hist_present++;
460 p->history[p->hist_present] = c;
461}
462
463/* returns 0 if successfull, 1 if there was no history to undo */
464int
465playlist_undo(playlist *p)
466{
467 playlist_changeset *c;
468
469 if (p->hist_present == -1)
470 return 1;
471
472 c = p->history[p->hist_present];
473
474 switch (c->type) {
475 case CHANGE_ADD0:
476 playlist_files_remove(p, c->location, c->size, false0);
477 break;
478 case CHANGE_REMOVE1:
479 playlist_files_add(p, c->files, c->location, c->size, false0);
480 break;
481 default:
482 errx(1, "%s: invalid change type", __FUNCTION__);
483 }
484
485 p->hist_present--;
486 return 0;
487}
488
489/* returns 0 if successfull, 1 if there was no history to re-do */
490int
491playlist_redo(playlist *p)
492{
493 playlist_changeset *c;
494
495 if (p->hist_present == history_size - 1
496 || p->history[p->hist_present + 1] == NULL((void *)0))
497 return 1;
498
499 c = p->history[p->hist_present + 1];
500
501 switch (c->type) {
502 case CHANGE_ADD0:
503 playlist_files_add(p, c->files, c->location, c->size, false0);
504 break;
505 case CHANGE_REMOVE1:
506 playlist_files_remove(p, c->location, c->size, false0);
507 break;
508 default:
509 errx(1, "%s: invalid change type", __FUNCTION__);
510 }
511
512 p->hist_present++;
513 return 0;
514}