1 /*
2 * function: ReplayGain support for Wave files (http://www.replaygain.org)
3 *
4 * Reads in a Vorbis file, figures out the peak and ReplayGain levels and
5 * applies the Gain tags to the Wave file
6 *
7 * This program is distributed under the GNU General Public License, version
8 * 2.1. A copy of this license is included with this source.
9 *
10 * Copyright (C) 2002-2004 John Edwards
11 * Additional code by Magnus Holmgren and Gian-Carlo Pascutto
12 * Linux patch by Marc Brooker
13 */
14
15 #include "config.h"
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <math.h>
19 #include <string.h>
20 #include <ctype.h>
21
22 #include <fcntl.h>
23
24 #include "gain_analysis.h"
25 #include "i18n.h"
26 #include "getopt.h"
27 #include "misc.h"
28 #include "audio.h"
29 #include "dither.h"
30 #include "main.h"
31 #include "wavegain.h"
32
33 #ifdef _WIN32
34 #include <windows.h>
35 #else
36 #include <unistd.h>
37 #endif
38
39 #ifdef ENABLE_RECURSIVE
40 #include "recurse.h"
41 #endif
42
43 /*Gcc uses LL as a suffix for long long int (64 bit) types - Marc Brooker 8/4/2004*/
44 #ifdef __GNUC__
45 #define ROUND64(x) ( doubletmp = (x) + Dither.Add + (Int64_t)0x001FFFFD80000000LL, *(Int64_t*)(&doubletmp) - (Int64_t)0x433FFFFD80000000LL )
46 #else
47 #define ROUND64(x) ( doubletmp = (x) + Dither.Add + (Int64_t)0x001FFFFD80000000L, *(Int64_t*)(&doubletmp) - (Int64_t)0x433FFFFD80000000L )
48 #endif
49
50 #include <errno.h>
51 static int xrename(const char *oldpath, const char *newpath);
52
53 extern int write_to_log;
54 dither_t Dither;
55 double doubletmp;
56 double total_samples;
57 double total_files;
58
59 /* Replaced with a double based function for consistency 2005-11-17
60 static float FABS(float x)
61 {
62 unsigned int *ix=(unsigned int *)&x;
63 *ix&=0x7fffffffUL;
64 return(x);
65 }
66 */
DABS(double x)67 static double DABS(double x)
68 {
69 Uint64_t *ix=(Uint64_t *)&x;
70 #ifdef __GNUC__
71 *ix&=0x7fffffffffffffffULL;
72 #else
73 *ix&=0x7fffffffffffffff;
74 #endif
75 return(x);
76 }
77
78 /* Dither output */
dither_output(int dithering,int shapingtype,int i,double Sum,int k,int format)79 Int64_t dither_output(int dithering, int shapingtype, int i, double Sum, int k, int format)
80 {
81 double Sum2;
82 Int64_t val;
83 if(dithering) {
84 if(!shapingtype) {
85 double tmp = Random_Equi ( Dither.Dither );
86 Sum2 = tmp - Dither.LastRandomNumber [k];
87 Dither.LastRandomNumber [k] = (int)tmp;
88 Sum2 = Sum += Sum2;
89 val = ROUND64 (Sum2) & Dither.Mask;
90 }
91 else {
92 Sum2 = Random_Triangular ( Dither.Dither ) - scalar16 ( Dither.DitherHistory[k], Dither.FilterCoeff + i );
93 Sum += Dither.DitherHistory [k] [(-1-i)&15] = (float)Sum2;
94 Sum2 = Sum + scalar16 ( Dither.ErrorHistory [k], Dither.FilterCoeff + i );
95 val = ROUND64 (Sum2) & Dither.Mask;
96 Dither.ErrorHistory [k] [(-1-i)&15] = (float)(Sum - val);
97 }
98 }
99 else
100 val = (Int64_t)(ROUND64 (Sum));
101
102 if (format == WAV_FMT_8BIT)
103 val = val >> 24;
104 else if (format == WAV_FMT_16BIT || format == WAV_FMT_AIFF)
105 val = val >> 16;
106 else if (format == WAV_FMT_24BIT)
107 val = val >> 8;
108
109 return (val);
110 }
111
112 /* Get the gain and peak value for a file. Runs in audiophile mode if
113 * audiophile is true.
114 *
115 * If an error occured, 0 is returned (a message has been printed).
116 */
117
get_gain(const char * filename,double * track_peak,double * track_gain,double * dc_offset,double * offset,SETTINGS * settings)118 int get_gain(const char *filename, double *track_peak, double *track_gain,
119 double *dc_offset, double *offset, SETTINGS *settings)
120 {
121 wavegain_opt *wg_opts = malloc(sizeof(wavegain_opt));
122 FILE *infile;
123 int result = 0;
124 double new_peak,
125 factor_clip,
126 scale,
127 peak = 0.,
128 dB;
129 int k, i;
130 long chunk;
131 input_format *format;
132
133 memset(wg_opts, 0, sizeof(wavegain_opt));
134
135 wg_opts->force = settings->force;
136
137 if(!strcmp(filename, "-")) {
138 infile = stdin;
139 settings->apply_gain = 0;
140 wg_opts->std_in = 1;
141 #ifdef _WIN32
142 _setmode( _fileno(stdin), _O_BINARY );
143 #endif
144 }
145 else
146 infile = fopen(filename, "rb");
147
148 if (infile == NULL) {
149 fprintf (stderr, " Not able to open input file %s.\n", filename) ;
150 goto exit;
151 }
152 wg_opts->apply_gain = 0;
153
154 /*
155 * Now, we need to select an input audio format
156 */
157
158 format = open_audio_file(infile, wg_opts);
159 if (!format) {
160 /* error reported by reader */
161 fprintf (stderr, " Unrecognized file format for %s.\n", filename);
162 goto exit;
163 }
164
165 if (wg_opts->gain_chunk == 1 && !wg_opts->force) {
166 fprintf (stderr, " Skipping File %s, it has already been processed.\n", filename);
167 // result = 1;
168 goto exit;
169 }
170
171 if ((wg_opts->channels != 1) && (wg_opts->channels != 2)) {
172 fprintf(stderr, " Unsupported number of channels.\n");
173 goto exit;
174 }
175
176 /* Only initialize gain analysis once in audiophile mode */
177 if (settings->first_file || !settings->audiophile) {
178 if (InitGainAnalysis(wg_opts->rate) != INIT_GAIN_ANALYSIS_OK) {
179 fprintf(stderr, " Error Initializing Gain Analysis (nonstandard samplerate?)\n");
180 goto exit;
181 }
182 }
183
184 if (settings->first_file) {
185 total_samples = (double)wg_opts->total_samples_per_channel;
186 fprintf(stderr, "\n Analyzing...\n\n");
187 fprintf(stderr, " Gain | Peak | Scale | New Peak |Left DC|Right DC| Track\n");
188 fprintf(stderr, " | | | |Offset | Offset |\n");
189 fprintf(stderr, " --------------------------------------------------------------\n");
190 if(write_to_log) {
191 write_log("\n Analyzing...\n\n");
192 write_log(" Gain | Peak | Scale | New Peak |Left DC|Right DC| Track\n");
193 write_log(" | | | |Offset | Offset |\n");
194 write_log(" --------------------------------------------------------------\n");
195 }
196 settings->first_file = 0;
197 }
198 else
199 total_samples += (double)wg_opts->total_samples_per_channel;
200
201 if (settings->fast && (wg_opts->total_samples_per_channel * (wg_opts->samplesize / 8)
202 * wg_opts->channels > 8192000)) {
203
204 long samples_read;
205 double **buffer = malloc(sizeof(double *) * wg_opts->channels);
206
207 for (i = 0; i < wg_opts->channels; i++)
208 buffer[i] = malloc(BUFFER_LEN * sizeof(double));
209
210 chunk = ((wg_opts->total_samples_per_channel * (wg_opts->samplesize / 8) * wg_opts->channels) + 44) / 1200;
211
212 for(k = 100; k < 1100; k+=5) {
213
214 samples_read = wg_opts->read_samples(wg_opts->readdata, buffer, BUFFER_LEN,
215 settings->fast, chunk * k);
216 if (samples_read == 0) {
217 break;
218 }
219 else {
220 if (samples_read < 0) {
221 /* Error in the stream. Not a problem, just reporting it in case
222 * we (the app) cares. In this case, we don't
223 */
224 }
225 else {
226 int i;
227 int j;
228
229 for (i = 0; i < wg_opts->channels; i++) {
230 for (j = 0; j < samples_read; j++) {
231 buffer[i][j] *= 0x7fff;
232 if (DABS(buffer[i][j]) > peak)
233 peak = DABS(buffer[i][j]);
234 }
235 }
236
237 if (AnalyzeSamples(buffer[0], buffer[1], samples_read,
238 wg_opts->channels) != GAIN_ANALYSIS_OK) {
239 fprintf(stderr, " Error processing samples.\n");
240 for (i = 0; i < wg_opts->channels; i++)
241 if (buffer[i]) free(buffer[i]);
242 if (buffer) free(buffer);
243 goto exit;
244 }
245 }
246 }
247 }
248 for (i = 0; i < wg_opts->channels; i++)
249 if (buffer[i]) free(buffer[i]);
250 if (buffer) free(buffer);
251 }
252 else
253 {
254 long samples_read;
255 double **buffer = malloc(sizeof(double *) * wg_opts->channels);
256
257 for (i = 0; i < wg_opts->channels; i++)
258 buffer[i] = malloc(BUFFER_LEN * sizeof(double));
259
260 while (1) {
261
262 samples_read = wg_opts->read_samples(wg_opts->readdata, buffer, BUFFER_LEN, 0, 0);
263
264 if (samples_read == 0) {
265 break;
266 }
267 else {
268 if (samples_read < 0) {
269 /* Error in the stream. Not a problem, just reporting it in case
270 * we (the app) cares. In this case, we don't
271 */
272 }
273 else {
274 int i;
275 int j;
276
277 for (i = 0; i < wg_opts->channels; i++) {
278 for (j = 0; j < samples_read; j++) {
279 offset[i] += buffer[i][j];
280 buffer[i][j] *= 0x7fff;
281 if (DABS(buffer[i][j]) > peak)
282 peak = DABS(buffer[i][j]);
283 }
284 }
285
286 if (AnalyzeSamples(buffer[0], buffer[1], samples_read,
287 wg_opts->channels) != GAIN_ANALYSIS_OK) {
288 fprintf(stderr, " Error processing samples.\n");
289 for (i = 0; i < wg_opts->channels; i++)
290 if (buffer[i]) free(buffer[i]);
291 if (buffer) free(buffer);
292 goto exit;
293 }
294 }
295 }
296 }
297
298 for (i = 0; i < wg_opts->channels; i++) {
299 if (buffer[i]) free(buffer[i]);
300 dc_offset[i] = (double)(offset[i] / wg_opts->total_samples_per_channel);
301 }
302 if (buffer) free(buffer);
303 }
304 /*
305 * calculate factors for ReplayGain and ClippingPrevention
306 */
307 *track_gain = (GetTitleGain() + settings->man_gain);
308 scale = (pow(10., *track_gain * 0.05));
309 if(settings->clip_prev) {
310 factor_clip = (32767./( peak + 1));
311 if(scale < factor_clip)
312 factor_clip = 1.0;
313 else
314 factor_clip /= scale;
315 scale *= factor_clip;
316 }
317 new_peak = (peak * scale);
318
319 dB = 20. * log10(scale);
320 *track_gain = dB;
321 {
322 int dc_l;
323 int dc_r;
324 if (settings->no_offset) {
325 dc_l = 0;
326 dc_r = 0;
327 }
328 else {
329 dc_l = (int)(dc_offset[0] * 32768 * -1);
330 dc_r = (int)(dc_offset[1] * 32768 * -1);
331 }
332 fprintf(stderr, " %+6.2lf dB | %6.0lf | %5.2lf | %8.0lf | %4d | %4d | %s\n",
333 *track_gain, peak, scale, new_peak, dc_l, dc_r, filename);
334 if(write_to_log) {
335 write_log(" %+6.2lf dB | %6.0lf | %5.2lf | %8.0lf | %4d | %4d | %s\n",
336 *track_gain, peak, scale, new_peak, dc_l, dc_r, filename);
337 }
338 }
339 if (settings->scale && !settings->audiophile)
340 fprintf(stdout, "%8.6lf", scale);
341
342 settings->album_peak = settings->album_peak < peak ? peak : settings->album_peak;
343 *track_peak = new_peak;
344 result = 1;
345
346 exit:
347 if (result)
348 format->close_func(wg_opts->readdata);
349 if (wg_opts)
350 free(wg_opts);
351 if (infile)
352 fclose(infile);
353 return result;
354 }
355
356
357 /* Use the ReplayGain calculations to adjust the gain on the wave file.
358 * If audiophile_gain is selected, that value is used, otherwise the
359 * radio_gain value is used.
360 */
write_gains(const char * filename,double radio_gain,double audiophile_gain,double TitlePeak,double * dc_offset,double * album_dc_offset,SETTINGS * settings)361 int write_gains(const char *filename, double radio_gain, double audiophile_gain, double TitlePeak,
362 double *dc_offset, double *album_dc_offset, SETTINGS *settings)
363 {
364 wavegain_opt *wg_opts = malloc(sizeof(wavegain_opt));
365 FILE *infile;
366 audio_file *aufile;
367 int readcount,
368 result = 0,
369 delete_temp = 0,
370 i;
371 double Gain;
372 double scale;
373 double total_read = 0.;
374 double wrap_prev_pos;
375 double wrap_prev_neg;
376 void *sample_buffer;
377 input_format *format;
378
379 memset(wg_opts, 0, sizeof(wavegain_opt));
380
381 wg_opts->force = settings->force;
382 wg_opts->undo = settings->undo;
383
384 infile = fopen(filename, "rb");
385
386 if (infile == NULL) {
387 fprintf (stderr, " Not able to open input file %s.\n", filename) ;
388 goto exit;
389 }
390 wg_opts->apply_gain = 1;
391 wg_opts->write_chunk = settings->write_chunk;
392
393 /*
394 * Now, we need to select an input audio format
395 */
396
397 format = open_audio_file(infile, wg_opts);
398 if (!format) {
399 format->close_func(wg_opts->readdata);
400 if (wg_opts)
401 free(wg_opts);
402 fclose(infile);
403 /* error reported by reader */
404 fprintf (stderr, " Unrecognized file format for %s.\n", filename) ;
405 }
406 else if (wg_opts->undo && !wg_opts->gain_chunk) {
407 format->close_func(wg_opts->readdata);
408 if (wg_opts)
409 free(wg_opts);
410 fclose(infile);
411 fprintf(stderr, " Skipping file: %s - 'gain' chunk not found.\n", filename);
412 if (write_to_log) {
413 write_log(" Skipping file: %s - 'gain' chunk not found.\n", filename);
414 }
415 result = 1;
416 }
417 else if (wg_opts->gain_scale == 1.0 && !wg_opts->force) {
418 format->close_func(wg_opts->readdata);
419 if (wg_opts)
420 free(wg_opts);
421 fclose(infile);
422 fprintf(stderr, " Skipping file: %s - Gain already undone.\n", filename);
423 if (write_to_log) {
424 write_log(" Skipping file: %s - Gain already undone.\n", filename);
425 }
426 result = 1;
427 }
428 else {
429 double **pcm = malloc(sizeof(double *) * wg_opts->channels);
430
431 for (i = 0; i < wg_opts->channels; i++)
432 pcm[i] = malloc(BUFFER_LEN * sizeof(double));
433
434 switch(settings->format) {
435 case WAV_NO_FMT:
436 if (wg_opts->format == WAV_FMT_AIFF || wg_opts->format == WAV_FMT_AIFC8
437 || wg_opts->format == WAV_FMT_AIFC16) {
438 wg_opts->format = WAV_FMT_AIFF;
439 wrap_prev_pos = 0x7fff;
440 wrap_prev_neg = -0x8000;
441 }
442 else if (wg_opts->format == WAV_FMT_8BIT) {
443 wrap_prev_pos = 0x7f;
444 wrap_prev_neg = -0x80;
445 }
446 else if (wg_opts->format == WAV_FMT_16BIT) {
447 wrap_prev_pos = 0x7fff;
448 wrap_prev_neg = -0x8000;
449 }
450 else if (wg_opts->format == WAV_FMT_24BIT) {
451 wrap_prev_pos = 0x7fffff;
452 wrap_prev_neg = -0x800000;
453 }
454 else if (wg_opts->format == WAV_FMT_32BIT) {
455 wrap_prev_pos = 0x7fffffff;
456 wrap_prev_neg = -0x7fffffff;
457 }
458 else if (wg_opts->format == WAV_FMT_FLOAT) {
459 wrap_prev_pos = 1 - (1 / 0x80000000);
460 wrap_prev_neg = -1.;
461 }
462 break;
463 case WAV_FMT_8BIT:
464 wg_opts->format = WAV_FMT_8BIT;
465 wg_opts->samplesize = 8;
466 wrap_prev_pos = 0x7f;
467 wrap_prev_neg = -0x80;
468 break;
469 case WAV_FMT_AIFF:
470 wg_opts->format = WAV_FMT_AIFF;
471 wg_opts->samplesize = 16;
472 wg_opts->endianness = BIG;
473 wrap_prev_pos = 0x7fff;
474 wrap_prev_neg = -0x8000;
475 break;
476 case WAV_FMT_16BIT:
477 wg_opts->format = WAV_FMT_16BIT;
478 wg_opts->samplesize = 16;
479 wg_opts->endianness = LITTLE;
480 wrap_prev_pos = 0x7fff;
481 wrap_prev_neg = -0x8000;
482 break;
483 case WAV_FMT_24BIT:
484 wg_opts->format = WAV_FMT_24BIT;
485 wg_opts->samplesize = 24;
486 wg_opts->endianness = LITTLE;
487 wrap_prev_pos = 0x7fffff;
488 wrap_prev_neg = -0x800000;
489 break;
490 case WAV_FMT_32BIT:
491 wg_opts->format = WAV_FMT_32BIT;
492 wg_opts->samplesize = 32;
493 wg_opts->endianness = LITTLE;
494 wrap_prev_pos = 0x7fffffff;
495 wrap_prev_neg = -0x7fffffff;
496 break;
497 case WAV_FMT_FLOAT:
498 wg_opts->format = WAV_FMT_FLOAT;
499 wg_opts->samplesize = 32;
500 wg_opts->endianness = LITTLE;
501 wrap_prev_pos = 1 - (1 / 0x80000000);
502 wrap_prev_neg = -1.;
503 break;
504 }
505
506 wg_opts->std_out = settings->std_out;
507
508 aufile = open_output_audio_file(TEMP_NAME, wg_opts);
509
510 if (aufile == NULL) {
511 fprintf (stderr, " Not able to open output file %s.\n", TEMP_NAME);
512 fclose(infile);
513 goto exit;
514 }
515
516 Init_Dither (wg_opts->samplesize, settings->shapingtype);
517
518 if (wg_opts->undo) {
519 scale = 1.0 / wg_opts->gain_scale;
520 Gain = 20. * log10(scale);
521 wg_opts->gain_scale = 1.0;
522 }
523 else {
524 if (settings->audiophile)
525 Gain = audiophile_gain;
526 else
527 Gain = radio_gain;
528
529 scale = pow(10., Gain * 0.05);
530 wg_opts->gain_scale = scale;
531 }
532
533 fprintf(stderr, " \r");
534 fprintf(stderr, " Applying Gain of %+5.2lf dB to file: %s\n", Gain, filename);
535 if (write_to_log) {
536 write_log(" Applying Gain of %+5.2lf dB to file: %s\n", Gain, filename);
537 }
538
539 while (1) {
540
541 readcount = wg_opts->read_samples(wg_opts->readdata, pcm, BUFFER_LEN, 0, 0);
542
543 total_read += ((double)readcount / wg_opts->rate);
544 total_files += ((double)readcount / wg_opts->rate);
545 if( (long)total_files % 4 == 0) {
546 if (wg_opts->undo)
547 fprintf(stderr, "This file %3.0lf%% done\r",
548 total_read / (wg_opts->total_samples_per_channel / wg_opts->rate) * 100);
549 else
550 fprintf(stderr, "This file %3.0lf%% done\tAll files %3.0lf%% done\r",
551 total_read / (wg_opts->total_samples_per_channel / wg_opts->rate) * 100,
552 total_files / (total_samples / wg_opts->rate) * 100);
553 }
554
555 if (readcount == 0) {
556 break;
557 }
558 else if (readcount < 0) {
559 /* Error in the stream. Not a problem, just reporting it in case
560 * we (the app) cares. In this case, we don't
561 */
562 }
563 else {
564 int convsize = BUFFER_LEN;
565 int j,
566 i = 0,
567 k;
568 int bout = (readcount < convsize ? readcount : convsize);
569
570 /* scale doubles to 8, 16, 24 or 32 bit signed ints
571 * (host order) (unless float output)
572 * and apply ReplayGain scaling, etc.
573 */
574 sample_buffer = malloc(sizeof(double) * wg_opts->channels * bout);
575 for(k = 0; k < wg_opts->channels; k++) {
576 for(j = 0; j < bout; j++, i++) {
577 Int64_t val;
578 double Sum;
579
580 if (!settings->no_offset) {
581 if (settings->adc)
582 pcm[k][j] -= album_dc_offset[k];
583 else
584 pcm[k][j] -= dc_offset[k];
585 }
586
587 pcm[k][j] *= scale;
588 if (settings->limiter) { /* hard 6dB limiting */
589 if (pcm[k][j] < -0.5)
590 pcm[k][j] = tanh((pcm[k][j] + 0.5) / (1-0.5)) * (1-0.5) - 0.5;
591 else if (pcm[k][j] > 0.5)
592 pcm[k][j] = tanh((pcm[k][j] - 0.5) / (1-0.5)) * (1-0.5) + 0.5;
593 }
594 if (wg_opts->format != WAV_FMT_FLOAT) {
595 Sum = pcm[k][j]*2147483647.f;
596 if (i > 31)
597 i = 0;
598 val = dither_output(settings->dithering, settings->shapingtype, i,
599 Sum, k, wg_opts->format);
600 if (val > (Int64_t)wrap_prev_pos)
601 val = (Int64_t)wrap_prev_pos;
602 else if (val < (Int64_t)wrap_prev_neg)
603 val = (Int64_t)wrap_prev_neg;
604 pcm[k][j] = (double)val;
605 }
606 else {
607 if (pcm[k][j] > wrap_prev_pos)
608 pcm[k][j] = wrap_prev_pos;
609 else if (pcm[k][j] < wrap_prev_neg)
610 pcm[k][j] = wrap_prev_neg;
611 }
612 }
613 }
614 sample_buffer = output_to_PCM(pcm, sample_buffer, wg_opts->channels,
615 bout, wg_opts->format);
616 /* write to file */
617 write_audio_file(aufile, sample_buffer, bout * wg_opts->channels);
618
619 free(sample_buffer);
620 }
621 }
622 for (i = 0; i < wg_opts->channels; i++)
623 if (pcm[i]) free(pcm[i]);
624 if (pcm) free(pcm);
625 format->close_func(wg_opts->readdata);
626 close_audio_file(infile, aufile, wg_opts);
627 fclose(infile);
628
629 if (!settings->std_out) {
630 if (remove(filename) != 0) {
631 fprintf(stderr, " Error deleting old file '%s'\n", filename);
632 goto exit;
633 }
634
635 /*
636 * int rename(const char *old, const char *new);
637 * In POSIX, rename will fail if the 'old' and 'new' names are on different mounted file systems.
638 * ( From http://en.wikipedia.org/wiki/Rename_%28C%29 )
639 * Function 'xrename' from 'normalize-0.7.6' is one clever solution
640 */
641 /*if (rename(TEMP_NAME, filename) != 0) {*/
642 if (xrename(TEMP_NAME, filename) != 0) {
643 fprintf(stderr, " Error renaming '" TEMP_NAME "' to '%s' (uh-oh)\n", filename);
644 goto exit;
645 }
646 }
647 result = 1;
648 }
649 exit:
650 return result;
651 }
652
653 /* From normalize-0.7.6/nid3lib/write.c
654 * Move the file "oldpath" to "newpath", or copy and delete if they
655 * are on different filesystems.
656 */
657 static int
xrename(const char * oldpath,const char * newpath)658 xrename(const char *oldpath, const char *newpath)
659 {
660 FILE *in, *out;
661 char buf[4096];
662 size_t sz;
663
664 if (strcmp(oldpath, newpath) == 0)
665 return 0;
666
667 #ifdef __EMX__
668 if (unlink(newpath) == -1 && errno != ENOENT)
669 return -1;
670 #endif
671
672 if (rename(oldpath, newpath) == -1) {
673 if (errno == EXDEV) {
674 /* files are on different filesystems, so we have to copy */
675 if (unlink(newpath) == -1 && errno != ENOENT)
676 return -1;
677
678 in = fopen(oldpath, "rb");
679 if (in == NULL)
680 return -1;
681 out = fopen(newpath, "wb");
682 if (out == NULL) {
683 fclose(in);
684 return -1;
685 }
686
687 while ((sz = fread(buf, 1, 4096, in)) > 0)
688 fwrite(buf, 1, sz, out);
689
690 if (ferror(in) || ferror(out)) {
691 fclose(in);
692 fclose(out);
693 return -1;
694 }
695 if (fclose(in) == EOF) {
696 fclose(out);
697 return -1;
698 }
699 if (fclose(out) == EOF)
700 return -1;
701
702 if (unlink(oldpath) == -1)
703 return -1;
704 } else {
705 return -1;
706 }
707 }
708
709 return 0;
710 }
711