1 /*--------------------------------------------------------------------
2  *
3  *	Copyright (c) 1991-2021 by the GMT Team (https://www.generic-mapping-tools.org/team.html)
4  *	See LICENSE.TXT file for copying and redistribution conditions.
5  *
6  *      This program is free software; you can redistribute it and/or modify
7  *      it under the terms of the GNU Lesser General Public License as published by
8  *      the Free Software Foundation; version 3 or any later version.
9  *
10  *      This program is distributed in the hope that it will be useful,
11  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *      GNU Lesser General Public License for more details.
14  *
15  *	Contact info: www.generic-mapping-tools.org
16  *--------------------------------------------------------------------*/
17 /* Program:	gmt_remote.c
18  * Purpose:	routines involved in handling remote files and tiles
19  *
20  * Author:	Paul Wessel
21  * Date:	15-Sept-2017
22  */
23 
24 #include "gmt_dev.h"
25 #include "gmt_internals.h"
26 #include <curl/curl.h>
27 #ifdef WIN32
28 #include <sys/utime.h>
29 #endif
30 
31 #define GMT_HASH_INDEX	0
32 #define GMT_INFO_INDEX	1
33 
34 /* Copy a file from the GMT auto-download directory or the Internet.  We recognize
35  * different types and names of files.
36  * 1. There are data sets of use to all GMT users, such as global relief:
37  * 	   @earth_relief_<res>.grd	Various global relief grids
38  * We may add more data later but this is our start.
39  * 2. Data sets only used to run an example or a test script
40  * and these are all called @*, i.e., a '@' is pre-pended to the name.
41  * They live in a cache subdirectory under the GMT_DATA_SERVER
42  * and will be placed in a cache directory in the users ~/.gmt directory.
43  * 3. Generic URLs starting with http:, https:, or ftp:  These will be
44  * downloaded to the cache directory.
45  * If auto-download is enabled and a requested input file matches these
46  * names and not found by normal means then we download the file to the
47  * user-directory.  All places that open files (GMT_Read_Data) will do
48  * this check by first calling gmt_download_file_if_not_found.
49  */
50 
51 /* Need global variables for this black magic. Here is the problem:
52  * When a user accesses a large remote file and there is a power-outage
53  * or the user types Ctrl-C, the remote file is only partially copied
54  * over and is useless.  In those cases we should make sure we delete
55  * the incomplete file before killing ourselves.  Below is the implementation
56  * for Linux/macOS that handles these cases.  The actual CURL calls
57  * are bracketed by gmtremote_turn_on_ctrl_C_check and gmtremote_turn_off_ctrl_C_check which
58  * temporarily activates or deactivates our signal action on Ctrl-C.
59  * P. Wessel, June 30, 2019.
60  */
61 
62 #if !(defined(WIN32) || defined(NO_SIGHANDLER))
63 #define GMT_CATCH_CTRL_C
64 #include <signal.h>
65 struct sigaction cleanup_action, default_action;
66 char *file_to_delete_if_ctrl_C;
67 #endif
68 
69 struct LOCFILE_FP {
70 	char *file;	/* Pointer to file name */
71 	FILE *fp;	/* Open file pointer */
72 };
73 
gmtremote_delete_file_then_exit(int sig_no)74 GMT_LOCAL void gmtremote_delete_file_then_exit (int sig_no) {
75 	/* If we catch a CTRL-C during CURL download we must assume file is corrupted and remove it before exiting */
76 	gmt_M_unused (sig_no);
77 #ifdef GMT_CATCH_CTRL_C
78 #ifdef DEBUG
79 	fprintf (stderr, "Emergency removal of file %s due to Ctrl-C action\n", file_to_delete_if_ctrl_C);
80 #endif
81 	remove (file_to_delete_if_ctrl_C);	   /* Remove if we can, ignore any returns */
82 	sigaction (SIGINT, &default_action, NULL); /* Reset the default action */
83 	kill (0, SIGINT);			   /* Perform the final Ctrl-C action and die */
84 #endif
85 }
86 
gmtremote_turn_on_ctrl_C_check(char * file)87 GMT_LOCAL void gmtremote_turn_on_ctrl_C_check (char *file) {
88 #ifdef GMT_CATCH_CTRL_C
89 	file_to_delete_if_ctrl_C = file;			/* File to delete if CTRL-C is caught */
90 	gmt_M_memset (&cleanup_action, 1, struct sigaction);	/* Initialize the structure to NULL */
91 	cleanup_action.sa_handler = &gmtremote_delete_file_then_exit;	/* Set function we should call if CTRL-C is caught */
92 	sigaction(SIGINT, &cleanup_action, &default_action);	/* Activate the alternative signal checking */
93 #else
94 	gmt_M_unused (file);
95 #endif
96 }
97 
gmtremote_turn_off_ctrl_C_check()98 GMT_LOCAL void gmtremote_turn_off_ctrl_C_check () {
99 #ifdef GMT_CATCH_CTRL_C
100 	file_to_delete_if_ctrl_C = NULL;		/* Remove trace of any file name */
101 	sigaction (SIGINT, &default_action, NULL);	/* Reset default signal action */
102 #endif
103 }
104 
105 struct FtpFile {	/* Needed for argument to libcurl */
106 	const char *filename;	/* Name of file to write */
107 	FILE *fp;	/* File pointer to said file */
108 };
109 
gmtremote_throw_away(void * ptr,size_t size,size_t nmemb,void * data)110 GMT_LOCAL size_t gmtremote_throw_away (void *ptr, size_t size, size_t nmemb, void *data) {
111 	gmt_M_unused (ptr);
112 	gmt_M_unused (data);
113 	/* We are not interested in the headers itself,
114 	   so we only return the file size we would have saved ... */
115 	return (size_t)(size * nmemb);
116 }
117 
gmtremote_fwrite_callback(void * buffer,size_t size,size_t nmemb,void * stream)118 GMT_LOCAL size_t gmtremote_fwrite_callback (void *buffer, size_t size, size_t nmemb, void *stream) {
119 	struct FtpFile *out = (struct FtpFile *)stream;
120 	if (out == NULL) return GMT_NOERROR;	/* This cannot happen but Coverity fusses */
121 	if (!out->fp) { /* Open file for writing */
122 		out->fp = fopen (out->filename, "wb");
123 		if (!out->fp)
124 			return -1; /* failure, can't open file to write */
125 	}
126 	return fwrite (buffer, size, nmemb, out->fp);
127 }
128 
gmtremote_compare_names(const void * item_1,const void * item_2)129 GMT_LOCAL int gmtremote_compare_names (const void *item_1, const void *item_2) {
130 	/* Compare function used to sort the GMT_DATA_INFO array of structures into alphabetical order */
131 	const char *name_1 = ((struct GMT_DATA_INFO *)item_1)->file;
132 	const char *name_2 = ((struct GMT_DATA_INFO *)item_2)->file;
133 
134 	return (strcmp (name_1, name_2));
135 }
136 
gmtremote_parse_version(char * line)137 GMT_LOCAL int gmtremote_parse_version (char *line) {
138 	/* Parse a line like "# 6.1.0 or later GMT version required" and we will make no
139 	 * assumptions about how much space before the version. */
140 	int k = 1, start, major, minor, release;
141 	char text[GMT_LEN64] = {""};
142 	if (line[0] != '#') return 1;	/* Not a comment record! */
143 	strncpy (text, line, GMT_LEN64-1);
144 	while (isspace (text[k])) k++;	/* Skip until we get to the version */
145 	start = k;
146 	while (isdigit(text[k]) || text[k] == '.') k++;	/* Wind to end of version */
147 	text[k] = '\0';	/* Chop off the rest */
148 	if (sscanf (&text[start], "%d.%d.%d", &major, &minor, &release) != 3) return 1;
149 	if (major > GMT_MAJOR_VERSION) return 2;	/* Definitively too old */
150 	if (major < GMT_MAJOR_VERSION) return 0;	/* Should be fine */
151 	if (minor > GMT_MINOR_VERSION) return 2;	/* Definitively too old */
152 	if (minor < GMT_MINOR_VERSION) return 0;	/* Should be fine */
153 	if (release > GMT_RELEASE_VERSION) return 2;	/* Definitively too old */
154 	return GMT_NOERROR;
155 }
156 
gmtremote_remove_item(struct GMTAPI_CTRL * API,char * path,bool directory)157 GMT_LOCAL int gmtremote_remove_item (struct GMTAPI_CTRL *API, char *path, bool directory) {
158 	int error = GMT_NOERROR;
159 	if (directory) {	/* Delete populated directories via an operating system remove call */
160 		char del_cmd[PATH_MAX] = {""};
161 #ifdef _WIN32
162 		char *t = gmt_strrep (path, "/", "\\");	/* DOS rmdir needs paths with back-slashes */
163 		strcpy (del_cmd, "rmdir /s /q ");
164 		strncat (del_cmd, t, PATH_MAX-1);
165 		gmt_M_str_free (t);
166 #else
167 		sprintf (del_cmd, "rm -rf %s", path);
168 #endif
169 		if ((error = system (del_cmd))) {
170 			GMT_Report (API, GMT_MSG_ERROR, "Failed to remove %s [error = %d]\n", path, error);
171 			error = GMT_RUNTIME_ERROR;
172 		}
173 	}
174 	else	/* Just delete a single file  */
175 		gmt_remove_file (API->GMT, path);
176 	return error;
177 }
178 
gmtremote_data_load(struct GMTAPI_CTRL * API,int * n)179 GMT_LOCAL struct GMT_DATA_INFO *gmtremote_data_load (struct GMTAPI_CTRL *API, int *n) {
180 	/* Read contents of the info file into an array of structs */
181 	int k = 0, nr;
182 	FILE *fp = NULL;
183 	struct GMT_DATA_INFO *I = NULL;
184 	char unit, line[GMT_LEN512] = {""}, file[PATH_MAX] = {""}, *c = NULL;
185 	struct GMT_CTRL *GMT = API->GMT;
186 
187 	snprintf (file, PATH_MAX, "%s/server/%s", GMT->session.USERDIR, GMT_INFO_SERVER_FILE);
188 
189 	GMT_Report (API, GMT_MSG_DEBUG, "Load contents from %s\n", file);
190 	*n = 0;
191 	if ((fp = fopen (file, "r")) == NULL) {
192 		GMT_Report (API, GMT_MSG_ERROR, "Unable to open file %s\n", file);
193 		return NULL;
194 	}
195 	if (fgets (line, GMT_LEN256, fp) == NULL) {	/* Try to get first record */
196 		fclose (fp);
197 		GMT_Report (API, GMT_MSG_ERROR, "Read error first record in file %s\n", file);
198 		GMT_Report (API, GMT_MSG_ERROR, "Deleting %s so it can get regenerated - please try again\n", file);
199 		gmt_remove_file (GMT, file);
200 		return NULL;
201 	}
202 	*n = atoi (line);		/* Number of non-commented records to follow */
203 	if (*n <= 0 || *n > GMT_BIG_CHUNK) {	/* Probably not a good value */
204 		fclose (fp);
205 		GMT_Report (API, GMT_MSG_ERROR, "Bad record counter in file %s\n", file);
206 		GMT_Report (API, GMT_MSG_ERROR, "Deleting %s so it can get regenerated - please try again\n", file);
207 		gmt_remove_file (GMT, file);
208 		return NULL;
209 	}
210 	if (fgets (line, GMT_LEN256, fp) == NULL) {	/* Try to get second record */
211 		fclose (fp);
212 		GMT_Report (API, GMT_MSG_ERROR, "Read error second record in file %s\n", file);
213 		return NULL;
214 	}
215 	if ((k = gmtremote_parse_version (line))) {
216 		fclose (fp);
217 		if (k == 2)
218 			GMT_Report (API, GMT_MSG_NOTICE, "Your GMT version too old to use the remote data mechanism - please upgrade to %s or later\n", line);
219 		else
220 			GMT_Report (API, GMT_MSG_ERROR, "Unable to parse \"%s\" to extract GMT version\n", line);
221 		return NULL;
222 	}
223 	if ((I = gmt_M_memory (GMT, NULL, *n, struct GMT_DATA_INFO)) == NULL) {
224 		fclose (fp);
225 		GMT_Report (API, GMT_MSG_ERROR, "Unable to allocated %d GMT_DATA_INFO structures!\n", *n);
226 		return NULL;
227 	}
228 
229 	while (fgets (line, GMT_LEN512, fp) != NULL) {
230 		if (line[0] == '#') continue;	/* Skip any comments */
231 		if ((nr = sscanf (line, "%s %s %s %c %lg %lg %s %lg %s %s %s %s %[^\n]", I[k].dir, I[k].file, I[k].inc, &I[k].reg, &I[k].scale, &I[k].offset, I[k].size, &I[k].tile_size, I[k].date, I[k].coverage, I[k].filler, I[k].CPT, I[k].remark)) != 13) {
232 			GMT_Report (API, GMT_MSG_WARNING, "File %s should have 13 fields but only %d read for record %d - download error???\n", file, nr, k);
233 			gmt_M_free (GMT, I);
234 			fclose (fp);
235 			return NULL;
236 		}
237 		/* Extract some useful bits to have in separate variables */
238 		sscanf (I[k].inc, "%lg%c", &I[k].d_inc, &unit);
239 		if (unit == 'm') I[k].d_inc *= GMT_MIN2DEG;		/* E.g., 30m becomes 0.5 */
240 		else if (unit == 's') I[k].d_inc *= GMT_SEC2DEG;	/* E.g., 30s becomes 0.00833333333333 */
241 		if ((c = strchr (I[k].file, '.')))	/* Get the file extension */
242 			strcpy (I[k].ext, c);
243 		if (I[k].tile_size > 0.0) {	/* A tiled dataset */
244 			size_t len = strlen (I[k].file);
245 			strncpy (I[k].tag, I[k].file, len-1);	/* Remote trailing slash */
246 		}
247 		k++;
248 	}
249 	fclose (fp);
250 
251 	if (k != *n) {
252 		GMT_Report (API, GMT_MSG_WARNING, "File %s said it has %d records but only found %d - download error???\n", file, *n, k);
253 		GMT_Report (API, GMT_MSG_WARNING, "File %s should be deleted.  Please try again\n", file);
254 		*n = 0;	/* Flag that excrement hit the fan */
255 	}
256 	/* Soft alphabetically on file names */
257 	qsort (I, *n, sizeof (struct GMT_DATA_INFO), gmtremote_compare_names);
258 	for (k = 0; k < *n; k++) I[k].id = k;	/* Give running number as ID in the sorted array */
259 
260 	if (GMT->current.io.new_data_list) {	/* Take this opportunity to delete datasets that are past their expiration date */
261 		time_t mod_time;
262 		struct tm *UTC = NULL;
263 		struct stat buf;
264 		int year, month, day, kyear, kmonth, kday;
265 		size_t L;
266 
267 		GMT->current.io.new_data_list = false;	/* We only do this once after a gmt_data_server.txt update */
268 		if (GMT->session.USERDIR == NULL) goto out_of_here;	/* Cannot have server data if no user directory is set */
269 		if (access (GMT->session.USERDIR, R_OK)) goto out_of_here;	/* Set, but have not made a user directory yet, so cannot have any remote data yet either */
270 
271 		for (k = 0; k < *n; k++) {	/* Check the release date of each data set that has been downloaded against the local file date */
272 			if (sscanf (I[k].date, "%d-%d-%d", &kyear, &kmonth, &kday) != 3) continue;	/* Maybe malformed datestring or on purpose to never check */
273 			snprintf (file, PATH_MAX, "%s/%s%s", GMT->session.USERDIR, I[k].dir, I[k].file);	/* Local path, may end in slash if a tile directory*/
274 			if ((L = strlen (file) - 1) && file[L] == '/') file[L] = '\0';	/* Chop off trailing / that indicates directory of tiles */
275 			if (access (file, R_OK)) continue;	/* No such file or directory yet */
276 			/* Here we have a local copy of this remote file or directory - we examine its creation date */
277 			if (stat (file, &buf)) {
278 				GMT_Report (API, GMT_MSG_WARNING, "Unable to get information about %s - skip\n", file);
279 				continue;
280 			}
281 			/*  Get its modification (creation) time */
282 #ifdef __APPLE__
283 			mod_time = buf.st_mtimespec.tv_sec;	/* Apple even has tv_nsec for nano-seconds... */
284 #else
285 			mod_time = buf.st_mtime;
286 #endif
287 			/* Extract the year, month, day integers */
288 			UTC   = gmtime (&mod_time);
289 			year  = UTC->tm_year + 1900;	/* Yep, how stupid is that, Y2K lovers. I guess 2030 might overflow a 32-bit int... */
290 			month = UTC->tm_mon + 1;		/* Make it 1-12 since it is 0-11 */
291 			day   = UTC->tm_mday;			/* Yep, lets start at 1 for days and 0 for months, makes sense */
292 			if (kyear < year) continue;	/* The origin year is older than our file so no need to check further */
293 			if (kyear == year) {	/* Our file and the server file is both from the same year */
294 				if (kmonth < month) continue;	/* The origin month is older than our copy so no need to check further */
295 				if (kmonth == month) {	/* Same year, same month, we are so close! */
296 					if (kday < day) continue; 	/* The origin day is older than our copy so no need to check further */
297 				}
298 			}
299 			/* If we get here we need to remove the outdated file or directory so we may download the latest on next try */
300 			if (gmtremote_remove_item (API, file, I[k].tile_size > 0.0)) {
301 				GMT_Report (GMT->parent, GMT_MSG_WARNING, "Unable to remove %s \n", file);
302 			}
303 		}
304 	}
305 
306 out_of_here:
307 	return (I);
308 };
309 
gmtremote_compare_key(const void * item_1,const void * item_2)310 GMT_LOCAL int gmtremote_compare_key (const void *item_1, const void *item_2) {
311 	/* We are passing string item_1 without any leading @ */
312 	const char *name_1 = (char *)item_1;
313 	const char *name_2 = ((struct GMT_DATA_INFO *)item_2)->file;
314 	size_t len = strlen (name_1);
315 
316 	return (strncmp (name_1, name_2, len));
317 }
318 
gmtremote_wind_to_file(const char * file)319 int gmtremote_wind_to_file (const char *file) {
320 	int k = strlen (file) - 2;	/* This jumps past any trailing / for tiles */
321 	while (k >= 0 && file[k] != '/') k--;
322 	return (k+1);
323 }
324 
gmt_remote_no_resolution_given(struct GMTAPI_CTRL * API,const char * rfile,int * registration)325 int gmt_remote_no_resolution_given (struct GMTAPI_CTRL *API, const char *rfile, int *registration) {
326 	/* Return first entry to a list of different resolutions for the
327 	 * same data set. For instance, if file is "earth_relief" then we
328 	 * return the ID to the first one listed. */
329 	char *c = NULL, *p = NULL, dir[GMT_LEN64] = {""}, file[GMT_LEN128] = {""};
330 	int ID = GMT_NOTSET, reg = GMT_NOTSET;
331 	size_t L;
332 
333 	if (rfile == NULL || rfile[0] == '\0') return GMT_NOTSET;	/* No file name given */
334 	if (rfile[0] != '@') return GMT_NOTSET;	/* No remote file name given */
335 	strcpy (file, &rfile[1]);	/* Make a copy but skipping leading @ character */
336 	if ((c = strchr (file, '+'))) c[0] = '\0';	/* Chop of modifiers such as in grdimage -I */
337 	L = strlen (file);
338 	if (!strncmp (&file[L-2], "_g", 2U)) {	/* Want a gridline-registered version */
339 		reg = GMT_GRID_NODE_REG;
340 		file[L-2] = '\0';
341 	}
342 	else if (!strncmp (&file[L-2], "_p", 2U)) {	/* Want a pixel-registered version */
343 		reg = GMT_GRID_PIXEL_REG;
344 		file[L-2] = '\0';
345 	}
346 	for (int k = 0; ID == GMT_NOTSET && k < API->n_remote_info; k++) {
347 		strncpy (dir, API->remote_info[k].dir, strlen (API->remote_info[k].dir)-1);	/* Make a copy without the trailing slash */
348 		p = strrchr (dir, '/');	/* Start of final subdirectory */
349 		p++;	/* Skip past the slash */
350 		if (!strcmp (p, file)) ID = k;
351 	}
352 	if (ID != GMT_NOTSET && registration)
353 		*registration = reg;	/* Pass back desired [or any] registration */
354 
355 	return (ID);	/* Start of the family or -1 */
356 }
357 
gmt_remote_resolutions(struct GMTAPI_CTRL * API,const char * rfile,unsigned int * n)358 struct GMT_RESOLUTION *gmt_remote_resolutions (struct GMTAPI_CTRL *API, const char *rfile, unsigned int *n) {
359 	/* Return list of available resolutions and registrations for the specified data set family.
360 	 * For instance, if file is "earth_relief" then we return an array of structs
361 	 * with the resolution and registration from 01d (g&p) to 91s (g) .*/
362 	char *c = NULL, *p = NULL, dir[GMT_LEN64] = {""}, file[GMT_LEN128] = {""};
363 	static char *registration = "gp";	/* The two types of registrations */
364 	int id = 0, reg = GMT_NOTSET;
365 	size_t L, n_alloc = GMT_SMALL_CHUNK;
366 	struct GMT_RESOLUTION *R = NULL;
367 
368 	if (rfile == NULL || rfile[0] == '\0') return NULL;	/* No file name given */
369 	if (rfile[0] != '@') return NULL;	/* No remote file name given */
370 	strcpy (file, &rfile[1]);	/* Make a copy but skipping leading @ character */
371 	if ((c = strchr (file, '+'))) c[0] = '\0';	/* Chop of modifiers such as in grdimage -I */
372 	L = strlen (file);
373 	if (!strncmp (&file[L-2], "_g", 2U)) {	/* Want a gridline-registered version */
374 		reg = GMT_GRID_NODE_REG;
375 		file[L-2] = '\0';
376 	}
377 	else if (!strncmp (&file[L-2], "_p", 2U)) {	/* Want a pixel-registered version */
378 		reg = GMT_GRID_PIXEL_REG;
379 		file[L-2] = '\0';
380 	}
381 	if ((R = gmt_M_memory (API->GMT, NULL, n_alloc, struct GMT_RESOLUTION)) == NULL)
382 		return NULL;	/* No memory */
383 
384 	for (int k = 0; k < API->n_remote_info; k++) {
385 		strncpy (dir, API->remote_info[k].dir, strlen (API->remote_info[k].dir)-1);	/* Make a copy without the trailing slash */
386 		p = strrchr (dir, '/');	/* Start of final subdirectory */
387 		p++;	/* Skip past the slash */
388 		if (!strcmp (p, file) && (reg == GMT_NOTSET || registration[reg] == API->remote_info[k].reg)) {	/* Got one to keep */
389 			R[id].resolution = urint (1.0 / API->remote_info[k].d_inc);	/* Number of nodes per degree */
390 			strncpy (R[id].inc, API->remote_info[k].inc, GMT_LEN8);	/* Copy the formatted inc string */
391 			R[id].reg = API->remote_info[k].reg;	/* Copy the registration */
392 			id++;
393 		}
394 		if (id == n_alloc) {	/* Need more memory */
395 			n_alloc += GMT_SMALL_CHUNK;
396 			if ((R = gmt_M_memory (API->GMT, NULL, n_alloc, struct GMT_RESOLUTION)) == NULL)
397 				return NULL;	/* No memory */
398 		}
399 	}
400 	if (id) {	/* Did find some */
401 		if ((R = gmt_M_memory (API->GMT, R, id, struct GMT_RESOLUTION)) == NULL)
402 			return NULL;	/* No memory */
403 		*n = id;
404 	}
405 	else {	/* No luck, probably filename typo */
406 		gmt_M_free (API->GMT, R);
407 		*n = 0;
408 	}
409 
410 	return (R);
411 }
412 
gmt_remote_dataset_id(struct GMTAPI_CTRL * API,const char * ifile)413 int gmt_remote_dataset_id (struct GMTAPI_CTRL *API, const char *ifile) {
414 	/* Return the entry in the remote file table of file is found, else -1.
415 	 * Complications to consider before finding a match:
416 	 * Input file may or may not have leading @
417 	 * Input file may or may not have _g or _p for registration
418 	 * Input file may or many not have an extension
419 	 * Key file may be a tiled data set and thus ends with '/'
420 	 */
421 	int pos = 0;
422 	char file[PATH_MAX] = {""};
423 	struct GMT_DATA_INFO *key = NULL;
424 	if (ifile == NULL || ifile[0] == '\0') return GMT_NOTSET;	/* No file name given */
425 	if (ifile[0] == '@') pos = 1;	/* Skip any leading remote flag */
426 	/* Exclude leading directory for local saved versions of the file */
427 	if (pos == 0) pos = gmtremote_wind_to_file (ifile);	/* Skip any leading directories */
428 	/* Must handle the use of srtm_relief vs earth_relief for the 01s and 03s data */
429 	if (strncmp (&ifile[pos], "srtm_relief_0", 13U) == 0)	/* Gave strm special name */
430 		sprintf (file, "earth_%s", &ifile[pos+5]);	/* Replace srtm with earth */
431 	else	/* Just copy as is from pos */
432 		strcpy (file, &ifile[pos]);
433 	key = bsearch (file, API->remote_info, API->n_remote_info, sizeof (struct GMT_DATA_INFO), gmtremote_compare_key);
434 	if (key) {	/* Make sure we actually got a real hit since file = "earth" will find a key starting with "earth****" */
435 		char *ckey = strrchr (key->file, '.');		/* Find location of the start of the key file extension (or NULL if no extension) */
436 		char *cfile = strrchr (file, '.');	/* Find location of the start of the input file extension (or NULL if no extension) */
437 		size_t Lfile = (cfile) ? (size_t)(cfile - file) : strlen (file);	/* Length of key file name without extension */
438 		size_t Lkey  = (ckey)  ? (size_t)(ckey  - key->file)  : strlen (key->file);		/* Length of key file name without extension */
439 		if (ckey == NULL && Lkey > 1 && key->file[Lkey-1] == '/') Lkey--;	/* Skip trailing dir flag */
440 		if (Lkey > Lfile && Lkey > 2 && key->file[Lkey-2] == '_' && strchr ("gp", key->file[Lkey-1])) Lkey -= 2;	/* Remove the length of _g or _p from Lkey */
441 		if (Lfile != Lkey)	/* Not an exact match (apart from trailing _p|g) */
442 			key = NULL;
443 	}
444 	return ((key == NULL) ? GMT_NOTSET : key->id);
445 }
446 
gmt_file_is_cache(struct GMTAPI_CTRL * API,const char * file)447 bool gmt_file_is_cache (struct GMTAPI_CTRL *API, const char *file) {
448 	/* Returns true if a remote file is a cache file */
449 	if (file == NULL || file[0] == '\0') return false;	/* Nothing given */
450 	if (gmt_M_file_is_memory (file)) return false;	/* Memory files are not remote */
451 	if (file[0] != '@') return false;	/* Cannot be a remote file, let alone cache */
452 	if (gmt_remote_dataset_id (API, file) != GMT_NOTSET) return false;	/* Found a remote dataset, but not cache */
453 	return true;
454 }
455 
gmt_set_unspecified_remote_registration(struct GMTAPI_CTRL * API,char ** file_ptr)456 void gmt_set_unspecified_remote_registration (struct GMTAPI_CTRL *API, char **file_ptr) {
457 	/* If a remote file is missing _g or _p we find which one we should use and revise the file accordingly.
458 	 * There are a few different scenarios where this can happen:
459 	 * 1. Users of GMT <= 6.0.0 are used to say earth_relief_01m. These will now get p.
460 	 * 2. Users who do not care about registration.  If so, they get p if available. */
461 	char newfile[GMT_LEN256] = {""}, reg[2] = {'p', 'g'}, *file = NULL, *infile = NULL, *ext = NULL, *c = NULL;
462 	int k_data, k;
463 	if (file_ptr == NULL || (file = *file_ptr) == NULL || file[0] == '\0') return;
464 	if (gmt_M_file_is_memory (file)) return;	/* Not a remote file for sure */
465 	if (file[0] != '@') return;
466 	infile = strdup (file);
467 	if ((c = strchr (infile, '+')))	/* Got modifiers, probably from grdimage or similar, chop off for now */
468 		c[0] = '\0';
469 	/* Deal with any extension the user may have added */
470 	ext = gmt_chop_ext (infile);
471 	/* If the remote file is found then there is nothing to do */
472 	if ((k_data = gmt_remote_dataset_id (API, infile)) == GMT_NOTSET) goto clean_up;
473 	if (strstr (file, "_p") || strstr (file, "_g")) goto clean_up;	/* Already have the registration codes */
474 	for (k = 0; k < 2; k++) {
475 		/* First see if this _<reg> version exists of this dataset */
476 		sprintf (newfile, "%s_%c", infile, reg[k]);
477 		if ((k_data = gmt_remote_dataset_id (API, newfile)) != GMT_NOTSET) {
478 			/* Found, replace given file name with this */
479 			if (c) {	/* Restore the modifiers */
480 				c[0] = '+';
481 				if (gmt_found_modifier (API->GMT, c, "os"))
482 					GMT_Report (API, GMT_MSG_WARNING, "Cannot append +s<scl> and/or +o<offset> to the remote global grid %s - ignored\n", newfile);
483 				else
484 					strcat (newfile, c);
485 			}
486 			gmt_M_str_free (*file_ptr);
487 			*file_ptr = strdup (newfile);
488 			goto clean_up;
489 		}
490 	}
491 clean_up:
492 	gmt_M_str_free (infile);
493 }
494 
gmt_remote_no_extension(struct GMTAPI_CTRL * API,const char * file)495 int gmt_remote_no_extension (struct GMTAPI_CTRL *API, const char *file) {
496 	int k_data = gmt_remote_dataset_id (API, file);
497 	if (k_data == GMT_NOTSET) return GMT_NOTSET;
498 	if (API->remote_info[k_data].ext[0] == '\0') return GMT_NOTSET;	/* Tiled grid */
499 	if (strstr (file, API->remote_info[k_data].ext)) return GMT_NOTSET;	/* Already has extension */
500 	return k_data;	/* Missing its extension */
501 }
502 
gmtremote_display_attribution(struct GMTAPI_CTRL * API,int key,const char * file,int tile)503 GMT_LOCAL void gmtremote_display_attribution (struct GMTAPI_CTRL *API, int key, const char *file, int tile) {
504 	/* Display a notice regarding the source of this data set */
505 	char *c = NULL, name[GMT_LEN128] = {""};
506 	if (key != GMT_NOTSET && !API->server_announced && !strchr (file, ':')) {	/* Server file has no http:// here */
507 		if ((c = strrchr (API->GMT->session.DATASERVER, '/')))	/* Found last slash in http:// */
508 			strcpy (name, ++c);
509 		else /* Just in case */
510 			strncpy (name, gmt_dataserver_url (API), GMT_LEN128-1);
511 		if ((c = strchr (name, '.'))) c[0] = '\0';	/* Chop off stuff after the initial name */
512 		gmt_str_toupper (name);
513 		GMT_Report (API, GMT_MSG_NOTICE, "Remote data courtesy of GMT data server %s [%s]\n\n", API->GMT->session.DATASERVER, gmt_dataserver_url (API));
514 		API->server_announced = true;
515 	}
516 	if (key == GMT_NOTSET) {	/* A Cache file */
517 		if (strchr (file, ':'))	/* Generic URL */
518 			GMT_Report (API, GMT_MSG_INFORMATION, "  -> Download URL file: %s\n", file);
519 		else
520 			GMT_Report (API, GMT_MSG_INFORMATION, "  -> Download cache file: %s\n", file);
521 	}
522 	else {	/* Remote data sets */
523 		if (!API->remote_info[key].used) {
524 			GMT_Report (API, GMT_MSG_NOTICE, "%s.\n", API->remote_info[key].remark);
525 			API->remote_info[key].used = true;
526 		}
527 		if (tile) {	/* Temporarily remote the trailing slash when printing the dataset name */
528 			c = strrchr (API->remote_info[key].file, '/');
529 			c[0] = '\0';
530 			strncpy (name, &file[1], 7U);	name[7] = '\0';
531 			GMT_Report (API, GMT_MSG_NOTICE, "  -> Download %lgx%lg degree grid tile (%s): %s\n",
532 					API->remote_info[key].tile_size, API->remote_info[key].tile_size, API->remote_info[key].file, name);
533 			c[0] = '/';
534 		}
535 		else
536 			GMT_Report (API, GMT_MSG_NOTICE, "  -> Download grid file [%s]: %s\n", API->remote_info[key].size, API->remote_info[key].file);
537 	}
538 }
539 
gmtremote_find_and_give_data_attribution(struct GMTAPI_CTRL * API,const char * file)540 GMT_LOCAL int gmtremote_find_and_give_data_attribution (struct GMTAPI_CTRL *API, const char *file) {
541 	/* Print attribution when the @remotefile is downloaded for the first time */
542 	char *c = NULL;
543 	int match, tile = 0;
544 
545 	if (file == NULL || file[0] == '\0') return GMT_NOTSET;	/* No file name given */
546 	if ((c = strstr (file, ".grd")) || (c = strstr (file, ".tif")))	/* Ignore extension in comparison */
547 		c[0] = '\0';
548 	if ((match = gmt_remote_dataset_id (API, file)) == GMT_NOTSET) {	/* Check if it is a tile */
549 		if ((match = gmt_file_is_a_tile (API, file, GMT_LOCAL_DIR)) != GMT_NOTSET)	/* Got a remote tile */
550 			tile = 1;
551 	}
552 	gmtremote_display_attribution (API, match, file, tile);
553 	if (c) c[0] = '.';	/* Restore extension */
554 	return (match);
555 }
556 
gmtremote_lockfile(struct GMT_CTRL * GMT,char * file)557 GMT_LOCAL char *gmtremote_lockfile (struct GMT_CTRL *GMT, char *file) {
558 	/* Create a dummy file in temp with extension .download and use as a lock file */
559 	char *c = strrchr (file, '/');
560 	char Lfile[PATH_MAX] = {""};
561 	if (c)	/* Found the last slash, skip it */
562 		c++;
563 	else	/* No path, just point to file */
564 		c = file;
565 	if (c[0] == '@') c++;	/* Skip any leading @ sign */
566 	sprintf (Lfile, "%s/%s.download", GMT->parent->tmp_dir, c);
567 	return (strdup (Lfile));
568 }
569 
gmtremote_skip_large_files(struct GMT_CTRL * GMT,char * URL,size_t limit)570 GMT_LOCAL size_t gmtremote_skip_large_files (struct GMT_CTRL *GMT, char * URL, size_t limit) {
571 	/* Get the remote file's size and if too large we refuse to download */
572 	CURL *curl = NULL;
573 	CURLcode res;
574 	double filesize = 0.0;
575 	size_t action = 0;
576 
577 	if (limit == 0) return 0;	/* Download regardless of size */
578 	curl_global_init (CURL_GLOBAL_DEFAULT);
579 
580 	if ((curl = curl_easy_init ())) {
581 		curl_easy_setopt (curl, CURLOPT_URL, URL);
582 		/* Do not download the file */
583 		curl_easy_setopt (curl, CURLOPT_NOBODY, 1L);
584 		/* Tell libcurl to not verify the peer */
585 		curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
586 		/* Tell libcurl to fail on 4xx responses (e.g. 404) */
587 		curl_easy_setopt (curl, CURLOPT_FAILONERROR, 1L);
588 		/* Tell libcurl to follow 30x redirects */
589 		curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1L);
590 		/* No header output: TODO 14.1 http-style HEAD output for ftp */
591 		curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, gmtremote_throw_away);
592 		curl_easy_setopt (curl, CURLOPT_HEADER, 0L);
593 		/* Complete connection within 10 seconds */
594 		 curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, GMT_CONNECT_TIME_OUT);
595 
596 		res = curl_easy_perform (curl);
597 
598 		if ((res = curl_easy_perform (curl)) == CURLE_OK) {
599       			res = curl_easy_getinfo (curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &filesize);
600 			if ((res == CURLE_OK) && (filesize > 0.0)) {	/* Got the size */
601 				GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Remote file %s: Size is %0.0f bytes\n", URL, filesize);
602 				action = (filesize < (double)limit) ? 0 : (size_t)filesize;
603 			}
604 		}
605 		else	/* We failed */
606 			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Remote file %s: Curl returned error %d\n", URL, res);
607 
608 		/* always cleanup */
609 		curl_easy_cleanup (curl);
610 	}
611 
612 	curl_global_cleanup ();
613 
614 	return action;
615 }
616 
gmtremote_setup_curl(struct GMTAPI_CTRL * API,char * url,char * local_file,struct FtpFile * urlfile,unsigned int time_out)617 CURL * gmtremote_setup_curl (struct GMTAPI_CTRL *API, char *url, char *local_file, struct FtpFile *urlfile, unsigned int time_out) {
618 	/* Single function that sets up an impending CURL operation */
619 	CURL *Curl = NULL;
620 	if ((Curl = curl_easy_init ()) == NULL) {
621 		GMT_Report (API, GMT_MSG_ERROR, "Failed to initiate curl - cannot obtain %s\n", url);
622 		return NULL;
623 	}
624 	if (curl_easy_setopt (Curl, CURLOPT_SSL_VERIFYPEER, 0L)) {		/* Tell libcurl to not verify the peer */
625 		GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to not verify the peer\n");
626 		return NULL;
627 	}
628 	if (curl_easy_setopt (Curl, CURLOPT_FOLLOWLOCATION, 1L)) {		/* Tell libcurl to follow 30x redirects */
629 		GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to follow redirects\n");
630 		return NULL;
631 	}
632 	if (curl_easy_setopt (Curl, CURLOPT_FAILONERROR, 1L)) {		/* Tell libcurl to fail on 4xx responses (e.g. 404) */
633 		GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to fail for 4xx responses\n");
634 		return NULL;
635 	}
636  	if (curl_easy_setopt (Curl, CURLOPT_URL, url)) {	/* Set the URL to copy */
637 		GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to read from %s\n", url);
638 		return NULL;
639 	}
640  	if (curl_easy_setopt (Curl, CURLOPT_CONNECTTIMEOUT, GMT_CONNECT_TIME_OUT)) {	/* Set connection timeout to 10s [300] */
641 		GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to limit connection timeout to %lds\n", GMT_CONNECT_TIME_OUT);
642 		return NULL;
643 	}
644 	if (time_out) {	/* Set a timeout limit */
645 		if (curl_easy_setopt (Curl, CURLOPT_TIMEOUT, time_out)) {
646 			GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to time out after %d seconds\n", time_out);
647 			return NULL;
648 		}
649 	}
650 	urlfile->filename = local_file;	/* Set pointer to local filename */
651 	/* Define our callback to get called when there's data to be written */
652 	if (curl_easy_setopt (Curl, CURLOPT_WRITEFUNCTION, gmtremote_fwrite_callback)) {
653 		GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl output callback function\n");
654 		return NULL;
655 	}
656 	/* Set a pointer to our struct that will be passed to the callback function */
657 	if (curl_easy_setopt (Curl, CURLOPT_WRITEDATA, urlfile)) {
658 		GMT_Report (API, GMT_MSG_ERROR, "Failed to set curl option to write to %s\n", local_file);
659 		return NULL;
660 	}
661 
662 	return Curl;	/* Happily return the Curl pointer */
663 }
664 
gmtremote_lock_on(struct GMT_CTRL * GMT,char * file)665 struct LOCFILE_FP *gmtremote_lock_on (struct GMT_CTRL *GMT, char *file) {
666 	/* Creates filename for lock and activates the lock */
667 	struct LOCFILE_FP *P = gmt_M_memory (GMT, NULL, 1, struct LOCFILE_FP);
668 	P->file = gmtremote_lockfile (GMT, file);
669 	if ((P->fp = fopen (P->file, "w")) == NULL) {
670 		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Failed to create lock file %s\n", P->file);
671 		gmt_M_str_free (P->file);
672 		gmt_M_free (GMT, P);
673 		return NULL;
674 	}
675 	gmtlib_file_lock (GMT, fileno(P->fp));	/* Attempt exclusive lock */
676 	return P;
677 }
678 
gmtremote_lock_off(struct GMT_CTRL * GMT,struct LOCFILE_FP ** P)679 void gmtremote_lock_off (struct GMT_CTRL *GMT, struct LOCFILE_FP **P) {
680 	/* Deactivates the lock on the file */
681 	gmtlib_file_unlock (GMT, fileno((*P)->fp));
682 	fclose ((*P)->fp);
683 	gmt_remove_file (GMT, (*P)->file);
684 	gmt_M_str_free ((*P)->file);
685 	gmt_M_free (GMT, *P);
686 }
687 
688 /* Deal with hash values of cache/data files */
689 
gmtremote_get_url(struct GMT_CTRL * GMT,char * url,char * file,char * orig,unsigned int index,bool do_lock)690 GMT_LOCAL int gmtremote_get_url (struct GMT_CTRL *GMT, char *url, char *file, char *orig, unsigned int index, bool do_lock) {
691 	bool turn_ctrl_C_off = false;
692 	int curl_err = 0, error = GMT_NOERROR;
693 	long time_spent;
694 	CURL *Curl = NULL;
695 	struct LOCFILE_FP *LF = NULL;
696 	struct FtpFile urlfile = {NULL, NULL};
697 	struct GMTAPI_CTRL *API = GMT->parent;
698 	time_t begin, end;
699 
700 	if (GMT->current.setting.auto_download == GMT_NO_DOWNLOAD) {  /* Not allowed to use remote copying */
701 		GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Remote download is currently deactivated\n");
702 		return 1;
703 	}
704 	if (GMT->current.io.internet_error) return 1;   			/* Not able to use remote copying in this session */
705 
706 	/* Make a lock */
707 	if (do_lock && (LF = gmtremote_lock_on (GMT, file)) == NULL)
708 		return 1;
709 
710 	/* If file locking held us up as another process was downloading the same file,
711 	 * then that file should now be available.  So we check again if it is before proceeding */
712 
713 	if (do_lock && !access (file, F_OK))
714 		goto unlocking1;	/* Yes it was, unlock and return no error */
715 
716 	/* Initialize the curl session */
717 	if ((Curl = gmtremote_setup_curl (API, url, file, &urlfile, GMT_HASH_TIME_OUT)) == NULL)
718 		goto unlocking1;
719 
720 	GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Downloading file %s ...\n", url);
721 	gmtremote_turn_on_ctrl_C_check (file);	turn_ctrl_C_off = true;
722 	begin = time (NULL);
723 	if ((curl_err = curl_easy_perform (Curl))) {	/* Failed, give error message */
724 		end = time (NULL);
725 		time_spent = (long)(end - begin);
726 		GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Unable to download file %s\n", url);
727 		GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "Libcurl Error: %s\n", curl_easy_strerror (curl_err));
728 		if (urlfile.fp != NULL) {
729 			fclose (urlfile.fp);
730 			urlfile.fp = NULL;
731 		}
732 		if (time_spent >= GMT_HASH_TIME_OUT) {	/* Ten seconds is too long time - server down? */
733 			GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "GMT data server may be down - delay checking hash file for 24 hours\n");
734 			GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "You can turn remote file download off by setting GMT_DATA_UPDATE_INTERVAL to \"off\"\n");
735 			if (orig && !access (orig, F_OK)) {	/* Refresh modification time of original hash file */
736 #ifdef WIN32
737 				_utime (orig, NULL);
738 #else
739 				utimes (orig, NULL);
740 #endif
741 				GMT->current.io.refreshed[index] = GMT->current.io.internet_error = true;
742 			}
743 		}
744 		error = 1; goto unlocking1;
745 	}
746 	curl_easy_cleanup (Curl);
747 	if (urlfile.fp) /* close the local file */
748 		fclose (urlfile.fp);
749 
750 unlocking1:
751 
752 	/* Remove lock file after successful download */
753 	if (do_lock) gmtremote_lock_off (GMT, &LF);
754 
755 	if (turn_ctrl_C_off) gmtremote_turn_off_ctrl_C_check ();
756 
757 	return error;
758 }
759 
gmtremote_hash_load(struct GMT_CTRL * GMT,char * file,int * n)760 GMT_LOCAL struct GMT_DATA_HASH *gmtremote_hash_load (struct GMT_CTRL *GMT, char *file, int *n) {
761 	/* Read contents of the hash file into an array of structs */
762 	int k;
763 	FILE *fp = NULL;
764 	struct GMT_DATA_HASH *L = NULL;
765 	char line[GMT_LEN256] = {""};
766 
767 	GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Load contents from %s\n", file);
768 	*n = 0;
769 	if ((fp = fopen (file, "r")) == NULL) return NULL;
770 	if (fgets (line, GMT_LEN256, fp) == NULL) {	/* Try to get first record */
771 		fclose (fp);
772 		return NULL;
773 	}
774 	*n = atoi (line);		/* Number of records to follow */
775 	if (*n <= 0 || *n > GMT_BIG_CHUNK) {	/* Probably not a good value */
776 		fclose (fp);
777 		return NULL;
778 	}
779 	L = gmt_M_memory (GMT, NULL, *n, struct GMT_DATA_HASH);
780 	for (k = 0; k < *n; k++) {
781 		if (fgets (line, GMT_LEN256, fp) == NULL) break;	/* Next record */
782 		sscanf (line, "%s %s %" PRIuS, L[k].name, L[k].hash, &L[k].size);
783 	}
784 	fclose (fp);
785 	if (k != *n) {
786 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "File %s said it has %d records but only found %d - download error???\n", file, *n, k);
787 		GMT_Report (GMT->parent, GMT_MSG_WARNING, "File %s will be deleted.  Please try again\n", file);
788 		*n = 0;	/* Flag that excrement hit the fan */
789 	}
790 	return (L);
791 };
792 
gmtremote_refresh(struct GMTAPI_CTRL * API,unsigned int index)793 GMT_LOCAL int gmtremote_refresh (struct GMTAPI_CTRL *API, unsigned int index) {
794 	/* This function is called every time we are about to access a @remotefile.
795 	 * It is called twice: Once for the hash table and once for the info table.
796 	 * First we check that we have the GMT_HASH_SERVER_FILE in the server directory.
797 	 * If we don't then we download it and return since no old file to compare to.
798 	 * If we do find the hash file then we get its creation time [st_mtime] as
799 	 * well as the current system time.  If the file is < GMT->current.setting.refresh_time
800 	 * days old we are done.
801 	 * If the file is older we rename it to *.old and download the latest hash file.
802 	 * This is the same for both values of index (hash and info).  For hash, we do more:
803 	 * Next, we load the contents of both files and do a double loop to find the
804 	 * entries for each old file in the new list, for all files.  If the old file
805 	 * is no longer in the list then we delete the data file.  If the hash code for
806 	 * a file has changed then we delete the local file so that the new versions
807 	 * will be downloaded from the server.  Otherwise we do nothing.
808 	 * The result of this is that any file(s) that have changed will be removed
809 	 * so that they must be downloaded again to get the new versions.
810 	 */
811 	struct stat buf;
812 	time_t mod_time, right_now = time (NULL);	/* Unix time right now */
813 	char indexpath[PATH_MAX] = {""}, old_indexpath[PATH_MAX] = {""}, new_indexpath[PATH_MAX] = {""}, url[PATH_MAX] = {""};
814 	const char *index_file = (index == GMT_HASH_INDEX) ? GMT_HASH_SERVER_FILE : GMT_INFO_SERVER_FILE;
815 	struct LOCFILE_FP *LF = NULL;
816 	struct GMT_CTRL *GMT = API->GMT;	/* Short hand */
817 
818 	if (GMT->current.io.refreshed[index]) return GMT_NOERROR;	/* Already been here */
819 
820 	snprintf (indexpath, PATH_MAX, "%s/server/%s", GMT->session.USERDIR, index_file);
821 
822 	if (access (indexpath, R_OK)) {    /* Not found locally so need to download the first time */
823 		char serverdir[PATH_MAX] = {""};
824 		snprintf (serverdir, PATH_MAX, "%s/server", GMT->session.USERDIR);
825 		if (access (serverdir, R_OK) && gmt_mkdir (serverdir)) {
826 			GMT_Report (API, GMT_MSG_ERROR, "Unable to create GMT server directory : %s\n", serverdir);
827 			return 1;
828 		}
829 		snprintf (url, PATH_MAX, "%s/%s", gmt_dataserver_url (API), index_file);
830 		GMT_Report (API, GMT_MSG_DEBUG, "Download remote file %s for the first time\n", url);
831 		if (gmtremote_get_url (GMT, url, indexpath, NULL, index, true)) {
832 			GMT_Report (API, GMT_MSG_INFORMATION, "Failed to get remote file %s\n", url);
833 			if (!access (indexpath, F_OK)) gmt_remove_file (GMT, indexpath);	/* Remove index file just in case it got corrupted or zero size */
834 			GMT->current.setting.auto_download = GMT_NO_DOWNLOAD;	/* Temporarily turn off auto download in this session only */
835 			GMT->current.io.internet_error = true;		/* No point trying again */
836 			return 1;
837 		}
838 		GMT->current.io.refreshed[index] = true;	/* Done our job */
839 		return GMT_NOERROR;
840 	}
841 	else
842 		GMT_Report (API, GMT_MSG_DEBUG, "Local file %s found\n", indexpath);
843 
844 	GMT->current.io.refreshed[index] = true;	/* Done our job */
845 
846 	/* Here we have the existing index file and its path is in indexpath. Check how old it is */
847 
848 	if (stat (indexpath, &buf)) {
849 		GMT_Report (API, GMT_MSG_ERROR, "Unable to get information about %s - abort\n", indexpath);
850 		return 1;
851 	}
852 	/*  Get its modification (creation) time */
853 #ifdef __APPLE__
854 	mod_time = buf.st_mtimespec.tv_sec;	/* Apple even has tv_nsec for nano-seconds... */
855 #else
856 	mod_time = buf.st_mtime;
857 #endif
858 
859 	if ((right_now - mod_time) > (GMT_DAY2SEC_I * GMT->current.setting.refresh_time)) {	/* Older than selected number of days; Time to get a new index file */
860 		GMT_Report (API, GMT_MSG_DEBUG, "File %s older than 24 hours, get latest from server.\n", indexpath);
861 		strcpy (new_indexpath, indexpath);	/* Duplicate path name */
862 		strcat (new_indexpath, ".new");		/* Append .new to the copied path */
863 		strcpy (old_indexpath, indexpath);	/* Duplicate path name */
864 		strcat (old_indexpath, ".old");		/* Append .old to the copied path */
865 		snprintf (url, PATH_MAX, "%s/%s", gmt_dataserver_url (API), index_file);	/* Set remote path to new index file */
866 
867 		/* Here we will try to download a file */
868 
869 		/* Make a lock on the file */
870 		if ((LF = gmtremote_lock_on (GMT, (char *)new_indexpath)) == NULL)
871 			return 1;
872 
873 		/* If file locking held us up as another process was downloading the same file,
874 		 * then that file should now be available.  So we check again if it is before proceeding */
875 
876 		if (!access (new_indexpath, F_OK)) {	/* Yes it was! Undo lock and return no error */
877 			gmtremote_lock_off (GMT, &LF);	/* Remove lock file after successful download (unless query) */
878 			return GMT_NOERROR;
879 		}
880 
881 		if (gmtremote_get_url (GMT, url, new_indexpath, indexpath, index, false)) {	/* Get the new index file from server */
882 			GMT_Report (API, GMT_MSG_DEBUG, "Failed to download %s - Internet troubles?\n", url);
883 			if (!access (new_indexpath, F_OK)) gmt_remove_file (GMT, new_indexpath);	/* Remove index file just in case it got corrupted or zero size */
884 			gmtremote_lock_off (GMT, &LF);
885 			return 1;	/* Unable to update the file (no Internet?) - skip the tests */
886 		}
887 		if (!access (old_indexpath, F_OK))
888 			remove (old_indexpath);	/* Remove old index file if it exists */
889 		GMT_Report (API, GMT_MSG_DEBUG, "Rename %s to %s\n", indexpath, old_indexpath);
890 		if (gmt_rename_file (GMT, indexpath, old_indexpath, GMT_RENAME_FILE)) {	/* Rename existing file to .old */
891 			GMT_Report (API, GMT_MSG_ERROR, "Failed to rename %s to %s.\n", indexpath, old_indexpath);
892 			gmtremote_lock_off (GMT, &LF);
893 			return 1;
894 		}
895 		GMT_Report (API, GMT_MSG_DEBUG, "Rename %s to %s\n", new_indexpath, indexpath);
896 		if (gmt_rename_file (GMT, new_indexpath, indexpath, GMT_RENAME_FILE)) {	/* Rename newly copied file to existing file */
897 			GMT_Report (API, GMT_MSG_ERROR, "Failed to rename %s to %s.\n", new_indexpath, indexpath);
898 			gmtremote_lock_off (GMT, &LF);
899 			return 1;
900 		}
901 
902 		if (index == GMT_HASH_INDEX) {	/* Special processing to upgrade or remove deprecated files */
903 			bool found;
904 			int nO, nN, n, o;
905 			struct GMT_DATA_HASH *O = NULL, *N = NULL;
906 
907 			if ((N = gmtremote_hash_load (GMT, indexpath, &nN)) == 0) {	/* Read in the new array of hash structs, will return 0 if mismatch of entries */
908 				gmt_remove_file (GMT, indexpath);	/* Remove corrupted index file */
909 				gmtremote_lock_off (GMT, &LF);
910 				return 1;
911 			}
912 
913 			O = gmtremote_hash_load (GMT, old_indexpath, &nO);	/* Read in the old array of hash structs */
914 			for (o = 0; o < nO; o++) {	/* Loop over items in old file */
915 				if (gmt_getdatapath (GMT, O[o].name, url, R_OK) == NULL) continue;	/* Don't have this file downloaded yet */
916 				/* Here the file was found locally and the full path is in the url */
917 				found = false;	/* Not found this file in the new list yet */
918 				for (n = 0; !found && n < nN; n++) {	/* Loop over items in new file */
919 					if (!strcmp (N[n].name, O[o].name)) {	/* File is in the current hash table */
920 						found = true;	/* We will exit this loop regardless of what happens next below */
921 						if (strcmp (N[n].hash, O[o].hash)) {	/* New hash differs from entry in hash old file */
922 							GMT_Report (API, GMT_MSG_DEBUG, "Server and cache versions of %s have different hash codes - must download new copy.\n", N[n].name);
923 							gmt_remove_file (GMT, url);	/* Need to re-download so be gone with it */
924 						}
925 						else {	/* Do size check */
926 							struct stat buf;
927 							if (stat (url, &buf)) {
928 								GMT_Report (API, GMT_MSG_WARNING, "Could not determine size of file %s.\n", url);
929 								continue;
930 							}
931 							if (N[n].size != (size_t)buf.st_size) {	/* Downloaded file size differ - need to re-download */
932 								GMT_Report (API, GMT_MSG_DEBUG, "Server and cache versions of %s have different byte sizes (%" PRIuS " versus %" PRIuS ") - must download new copy.\n", N[n].name, N[n].size, (size_t)buf.st_size);
933 								gmt_remove_file (GMT, url);	/* Need to re-download so be gone with it */
934 							}
935 							else
936 								GMT_Report (API, GMT_MSG_DEBUG, "Server and cache versions of %s are identical - no need to download new file.\n", N[n].name);
937 						}
938 					}
939 				}
940 				if (!found) {	/* This file was present locally but is no longer part of files on the server and should be removed */
941 					GMT_Report (API, GMT_MSG_DEBUG, "File %s no longer supported on server - deleting local copy.\n", O[o].name);
942 					gmt_remove_file (GMT, url);
943 				}
944 			}
945 			gmt_M_free (GMT, O);	/* Free old hash table structures */
946 			gmt_M_free (GMT, N);	/* Free new hash table structures */
947 			/* We now have an updated hash file */
948 			if (!access (old_indexpath, F_OK))
949 				remove (old_indexpath);	/* Remove old index file if it exists */
950 		}
951 		else
952 			GMT->current.io.new_data_list = true;	/* Flag that we wish to delete datasets older than entries in this file */
953 		/* Remove lock file after successful download */
954 		gmtremote_lock_off (GMT, &LF);
955 	}
956 	else
957 		GMT_Report (API, GMT_MSG_DEBUG, "File %s less than 24 hours old, refresh is premature.\n", indexpath);
958 	return GMT_NOERROR;
959 }
960 
gmt_refresh_server(struct GMTAPI_CTRL * API)961 void gmt_refresh_server (struct GMTAPI_CTRL *API) {
962 	/* Called once in gmt_begin from GMT_Create_Session,  The following actions take place:
963 	 *
964 	 * The data info table is refreshed if missing or older than 24 hours.
965 	 * The hash table is refreshed if missing or older than 24 hours.
966 	 *   If a new hash table is obtained and there is a previous one, we determine
967 	 *   if there are entries that have changed (e.g., newer, different files, different
968 	 *   sizes, or gone altogether).  In all these case we delete the file so that when the
969 	 *   user requests it, it forces a download of the updated file.
970 	 */
971 
972 	if (gmtremote_refresh (API, GMT_INFO_INDEX))	/* Watch out for changes on the server info once a day */
973 		GMT_Report (API, GMT_MSG_INFORMATION, "Unable to obtain remote information file %s\n", GMT_INFO_SERVER_FILE);
974 	else if (API->remote_info == NULL) {	/* Get server file attribution info if not yet loaded */
975 		if ((API->remote_info = gmtremote_data_load (API, &API->n_remote_info)) == NULL) {	/* Failed to load the info file */
976 			GMT_Report (API, GMT_MSG_INFORMATION, "Unable to read server information file\n");
977 		}
978 	}
979 
980 	if (gmtremote_refresh (API, GMT_HASH_INDEX)) {	/* Watch out for changes on the server hash once a day */
981 		GMT_Report (API, GMT_MSG_INFORMATION, "Unable to obtain remote hash table %s\n", GMT_HASH_SERVER_FILE);
982 	}
983 }
984 
gmtremote_switch_to_srtm(char * file,char * res)985 GMT_LOCAL char * gmtremote_switch_to_srtm (char *file, char *res) {
986 	/* There may be more than one remote Earth DEM product that needs to share the
987 	 * same 1x1 degree SRTM tiles.  This function handles this overlap; add more cases if needed. */
988 	char *c = NULL;
989 	if ((c = strstr (file, ".earth_relief_01s_g")) || (c = strstr (file, ".earth_synbath_01s_g")))
990 		*res = '1';
991 	else if ((c = strstr (file, ".earth_relief_03s_g")) || (c = strstr (file, ".earth_synbath_03s_g")))
992 		*res = '3';
993 	return (c);	/* Returns pointer to this "extension" or NULL */
994 }
995 
gmtremote_get_jp2_tilename(char * file)996 GMT_LOCAL char * gmtremote_get_jp2_tilename (char *file) {
997 	/* Must do special legacy checks for SRTMGL1|3 tag names for SRTM tiles.
998 	 * We also strip off the leading @ since we are building an URL for curl  */
999 	char res, *c = NULL, *new_file = NULL;
1000 
1001 	if ((c = gmtremote_switch_to_srtm (file, &res))) {
1002 		/* Found one of the SRTM tile families, now replace the tag with SRTMGL1|3 */
1003 		char remote_name[GMT_LEN64] = {""};
1004 		c[0] = '\0';	/* Temporarily chop off tag and beyond */
1005 		sprintf (remote_name, "%s.SRTMGL%c.%s", &file[1], res, GMT_TILE_EXTENSION_REMOTE);
1006 		c[0] = '.';	/* Restore period */
1007 		new_file = strdup (remote_name);
1008 	}
1009 	else
1010 		new_file = gmt_strrep (&file[1], GMT_TILE_EXTENSION_LOCAL, GMT_TILE_EXTENSION_REMOTE);
1011 	return (new_file);
1012 }
1013 
gmtremote_convert_jp2_to_nc(struct GMTAPI_CTRL * API,char * localfile)1014 GMT_LOCAL int gmtremote_convert_jp2_to_nc (struct GMTAPI_CTRL *API, char *localfile) {
1015 	static char *args = " -fg -Vq --IO_NC4_DEFLATION_LEVEL=9 --GMT_HISTORY=readonly";
1016 	char cmd[GMT_LEN512] = {""},  *ncfile = NULL;
1017 	int k_data;
1018 
1019 	if (API->GMT->current.io.leave_as_jp2) return GMT_NOERROR;	/* Conversion temporarily turned off by gmtget -N */
1020 	if ((k_data = gmtlib_file_is_jpeg2000_tile (API, localfile)) == GMT_NOTSET) return GMT_NOERROR;	/* Nothing to do */
1021 
1022 	/* Convert JP2 file to NC for local cache storage */
1023 	ncfile = gmt_strrep (localfile, GMT_TILE_EXTENSION_REMOTE, GMT_TILE_EXTENSION_LOCAL);
1024 	sprintf (cmd, "%s -G%s=ns", localfile, ncfile);	/* We know we are writing a netCDF short int grid */
1025 	if (!doubleAlmostEqual (API->remote_info[k_data].scale, 1.0) || !gmt_M_is_zero (API->remote_info[k_data].offset)) {
1026 		/* Integer is not the original data unit and/or has an offset - must scale/shift jp2 integers to units first.
1027 		 * Because we are inverting the scaling and because grdconvert applies z' = z * scale + offset, we must
1028 		 * pre-scale and change the sign of the offset here to get the translation we want */
1029 		char extra[GMT_LEN64] = {""};
1030 		sprintf (extra, "+s%g+o%g", API->remote_info[k_data].scale, API->remote_info[k_data].offset);
1031 		strcat (cmd, extra);	/* This will embed the scale and offset in the netCDF file so we can use the full range */
1032 		sprintf (extra, " -Z+s%g+o%g", API->remote_info[k_data].scale, -API->remote_info[k_data].offset / API->remote_info[k_data].scale);
1033 		strcat (cmd, extra);	/* This converts the integers we got back to Myr before we let netCDF do the offset/scaling above */
1034 	}
1035 	strcat (cmd, args);	/* Append the common arguments */
1036 	GMT_Report (API, GMT_MSG_INFORMATION, "Convert SRTM tile from JPEG2000 to netCDF grid [%s]\n", ncfile);
1037 	GMT_Report (API, GMT_MSG_DEBUG, "Running: grdconvert %sn", cmd);
1038 	if (GMT_Call_Module (API, "grdconvert", GMT_MODULE_CMD, cmd) != GMT_NOERROR) {
1039 		GMT_Report (API, GMT_MSG_ERROR, "ERROR - Unable to convert SRTM file %s to compressed netCDF format\n", localfile);
1040 		gmt_M_free (API->GMT, ncfile);
1041 		return GMT_RUNTIME_ERROR;
1042 	}
1043 	gmt_M_str_free (ncfile);
1044 	if (gmt_remove_file (API->GMT, localfile))
1045 		GMT_Report (API, GMT_MSG_WARNING, "Could not even remove file %s\n", localfile);
1046 	return GMT_NOERROR;
1047 }
1048 
gmt_set_remote_and_local_filenames(struct GMT_CTRL * GMT,const char * file,char * local_path,char * remote_path,unsigned int mode)1049 int gmt_set_remote_and_local_filenames (struct GMT_CTRL *GMT, const char * file, char *local_path, char *remote_path, unsigned int mode) {
1050 	/* Determines the remote and local files for any given file_name.
1051 	 * For remote files, the mode controls where they are written locally:
1052  	 *    0 : Place file where GMT wants it to be (e.g., server/earth/earth_relief, /cache etc depending on file type).
1053  	 *    1 : Place file in the cache directory
1054 	 *    2 : Place file in user directory
1055 	 *    3 : Place file in local (current) directory
1056 	 * If the file must be downloaded from a remote location then remote_path will be populated.
1057 	 * If the file exists in a local location then local_path will be populated and remote_path empty.
1058 	 * If both paths are set it means we want to download file and place it in local_path.
1059 	 * If a local file is not found we return an error code, else 0.
1060 	 */
1061 
1062 	int k_data = GMT_NOTSET, t_data = GMT_NOTSET;
1063 	unsigned int pos;
1064 	bool is_url = false, is_query = false, is_tile = false;
1065 	char was, *c = NULL, *jp2_file = NULL, *clean_file = NULL;
1066 	struct GMTAPI_CTRL *API = GMT->parent;
1067 
1068 	local_path[0] = remote_path[0] = '\0';
1069 
1070 	/* 0. Were we even given an argument? */
1071 	if (!file || !file[0]) return GMT_ARG_IS_NULL;   /* Got nutin' */
1072 
1073 	if (gmt_M_file_is_memory (file)) return GMT_NOERROR;	/* Memory location always exists */
1074 
1075 	if (gmtlib_found_url_for_gdal ((char *)file)) {	/* Special URLs for grids to be read via GDAL */
1076 		snprintf (remote_path, PATH_MAX, "%s", file);
1077 		pos = gmtlib_get_pos_of_filename (file);	/* Start of file in URL (> 0) */
1078 		snprintf (local_path, PATH_MAX, "%s", &file[pos]);	/* Same. No directives when writing the file */
1079 		return GMT_NOERROR;
1080 	}
1081 
1082 	/* 1. First handle full paths as given */
1083 #ifdef WIN32
1084 	if (file[0] == '/' || file[1] == ':')
1085 #else
1086 	if (file[0] == '/')
1087 #endif
1088 	{
1089 		clean_file = gmt_get_filename (API, file, gmtlib_valid_filemodifiers (GMT));	/* Strip off any file modifier or netCDF directives */
1090 		if (access (clean_file, F_OK)) {
1091 			GMT_Report (API, GMT_MSG_ERROR, "File %s was not found\n", file);
1092 			gmt_M_str_free (clean_file);
1093 			return GMT_FILE_NOT_FOUND;
1094 		}
1095 		if (access (clean_file, R_OK)) {
1096 			GMT_Report (API, GMT_MSG_ERROR, "File %s is not readable\n", file);
1097 			gmt_M_str_free (clean_file);
1098 			return GMT_BAD_PERMISSION;
1099 		}
1100 		gmt_M_str_free (clean_file);
1101 		strncpy (local_path, file, PATH_MAX-1);
1102 		remote_path[0] = '\0';	/* No need to get from elsewhere */
1103 		GMT_Report (API, GMT_MSG_DEBUG, "Given full path to file %s\n", local_path);
1104 		return GMT_NOERROR;
1105 	}
1106 
1107 	if (file[0] == '@') {	/* Either a cache file or a remote data set */
1108 		if ((k_data = gmt_remote_dataset_id (API, file)) != GMT_NOTSET) {
1109 			/* Got a valid remote server data filename and we know the local path to those */
1110 			if (GMT->session.USERDIR == NULL) goto not_local;	/* Cannot have server data if no user directory created yet */
1111 			snprintf (local_path, PATH_MAX, "%s", GMT->session.USERDIR);	/* This is the top-level directory for user data */
1112 			if (access (local_path, R_OK)) goto not_local;	/* Have not made a user directory yet, so cannot have the file yet either */
1113 			strcat (local_path, GMT->parent->remote_info[k_data].dir);	/* Append the subdir (/ or /server/earth/earth_relief/, etc) */
1114 			strcat (local_path, GMT->parent->remote_info[k_data].file);	/* Append filename */
1115 			if (access (local_path, R_OK)) goto not_local;	/* No such file yet */
1116 		}
1117 		else if ((t_data = gmt_file_is_a_tile (API, file, GMT_LOCAL_DIR)) != GMT_NOTSET) {	/* Got a remote tile */
1118 			/* Got a valid remote server tile filename and we know the local path to those */
1119 			if (GMT->session.USERDIR == NULL) goto not_local;	/* Cannot have server data if no user directory created yet */
1120 			snprintf (local_path, PATH_MAX, "%s", GMT->session.USERDIR);	/* This is the top-level directory for user data */
1121 			if (access (local_path, R_OK)) goto not_local;	/* Have not made a user directory yet, so cannot have the file yet either */
1122 			strcat (local_path, GMT->parent->remote_info[t_data].dir);	/* Append the subdir (/ or /server/earth/earth_relief/, etc) */
1123 			strcat (local_path, GMT->parent->remote_info[t_data].file);	/* Append the tiledir to get full path to dir for this type of tiles */
1124 			strcat (local_path, &file[1]);	/* Append filename */
1125 			is_tile = true;
1126 			if (access (local_path, R_OK)) {	/* A local tile in netCDF format was not found.  See if it exists as compressed JP2000 */
1127 				char *local_jp2 = gmt_strrep (local_path, GMT_TILE_EXTENSION_LOCAL, GMT_TILE_EXTENSION_REMOTE);
1128  				if (access (local_jp2, R_OK))
1129 	 				goto not_local;	/* No such file yet */
1130 				else {	/* Yep, do the just-in-time conversion now */
1131 					int error = gmtremote_convert_jp2_to_nc (API, local_jp2);
1132 					gmt_M_str_free (local_jp2);
1133 					if (error) return error;	/* Something failed in the conversion */
1134 				}
1135 			}
1136 		}
1137 		else {	/* Must be cache file */
1138 			if (GMT->session.CACHEDIR == NULL) goto not_local;	/* Cannot have cache data if no cache directory created yet */
1139 			clean_file = gmt_get_filename (API, file, gmtlib_valid_filemodifiers (GMT));	/* Strip off any file modifier or netCDF directives */
1140 			snprintf (local_path, PATH_MAX, "%s/%s", GMT->session.CACHEDIR, &clean_file[1]);	/* This is where all cache files live */
1141 			if ((c = strchr (local_path, '=')) || (c = strchr (local_path, '?'))) {
1142 				was = c[0];	c[0] = '\0';
1143 			}
1144 			GMT->parent->cache = true;
1145 			if (access (local_path, R_OK)) {
1146 				if (c) c[0] = was;
1147 				goto not_local;	/* No such file yet */
1148 			}
1149 			if (c) c[0] = was;
1150 		}
1151 		GMT_Report (API, GMT_MSG_DEBUG, "Remote file %s exists locally as %s\n", clean_file, local_path);
1152 		remote_path[0] = '\0';	/* No need to get from elsewhere */
1153 		if (clean_file)	gmt_M_str_free (clean_file);
1154 		return GMT_NOERROR;
1155 
1156 not_local:	/* Get here if we failed to find a remote file already on disk */
1157 		/* Set remote path */
1158 		if (is_tile) {	/* Tile not yet downloaded, but must switch to .jp2 format on the server (and deal with legacy SRTM tile tags) */
1159 			jp2_file = gmtremote_get_jp2_tilename ((char *)file);
1160 			snprintf (remote_path, PATH_MAX, "%s%s%s%s", gmt_dataserver_url (API), GMT->parent->remote_info[t_data].dir, GMT->parent->remote_info[t_data].file, jp2_file);
1161 		}
1162 		else if (k_data == GMT_NOTSET) {	/* Cache file not yet downloaded */
1163 			snprintf (remote_path, PATH_MAX, "%s/cache/%s", gmt_dataserver_url (API), &file[1]);
1164 			if (mode == 0) mode = GMT_CACHE_DIR;	/* Just so we default to the cache dir for cache files */
1165 		}
1166 		else	/* Remote data set */
1167 			snprintf (remote_path, PATH_MAX, "%s%s%s", gmt_dataserver_url (API), API->remote_info[k_data].dir, API->remote_info[k_data].file);
1168 
1169 		/* Set local path */
1170 		switch (mode) {
1171 			case GMT_CACHE_DIR:
1172 				if (GMT->session.CACHEDIR == NULL) {
1173 					GMT_Report (API, GMT_MSG_ERROR, "Cache directory storage requested for %s but your cache directory is undefined\n", file);
1174 					return GMT_FILE_NOT_FOUND;
1175 				}
1176 				else if (access (GMT->session.CACHEDIR, R_OK) && gmt_mkdir (GMT->session.CACHEDIR))
1177 					GMT_Report (API, GMT_MSG_ERROR, "Cache directory storage requested for %s but your cache directory could not be created\n", file);
1178 				else
1179 					snprintf (local_path, PATH_MAX, "%s/%s", GMT->session.CACHEDIR, &file[1]);
1180 				break;
1181 			case GMT_DATA_DIR:
1182 				if (GMT->session.USERDIR == NULL || access (GMT->session.USERDIR, R_OK))
1183 					GMT_Report (API, GMT_MSG_ERROR, "User directory storage requested for %s but your user directory is undefined or does not exist\n", file);
1184 				else
1185 					snprintf (local_path, PATH_MAX, "%s/%s", GMT->session.USERDIR, &file[1]);
1186 				break;
1187 			case GMT_LOCAL_DIR:
1188 				snprintf (local_path, PATH_MAX, "%s", &file[1]);
1189 				break;
1190 			default:	/* Place remote data files locally per the internal rules */
1191 				if (GMT->session.USERDIR == NULL || access (GMT->session.USERDIR, R_OK))
1192 					GMT_Report (API, GMT_MSG_ERROR, "User directory storage requested for %s but your user directory is undefined or does not exist\n", file);
1193 				else {	/* Have a user dir */
1194 					snprintf (local_path, PATH_MAX, "%s/server", GMT->session.USERDIR);
1195 					if (access (local_path, R_OK) && gmt_mkdir (local_path))	/* Have or just made a server subdirectory */
1196 						GMT_Report (API, GMT_MSG_ERROR, "Unable to create GMT data directory : %s\n", local_path);
1197 					if (is_tile) {	/* One of the tiles */
1198 						if (jp2_file) gmt_M_str_free (jp2_file);
1199 						jp2_file = gmt_strrep (&file[1], GMT_TILE_EXTENSION_LOCAL, GMT_TILE_EXTENSION_REMOTE);
1200 						snprintf (local_path, PATH_MAX, "%s%s%s", GMT->session.USERDIR, GMT->parent->remote_info[t_data].dir, GMT->parent->remote_info[t_data].file);
1201 						if (access (local_path, R_OK) && gmt_mkdir (local_path))	/* Have or just made a server/tile subdirectory */
1202 							GMT_Report (API, GMT_MSG_ERROR, "Unable to create GMT data directory : %s\n", local_path);
1203 						strcat (local_path, jp2_file);
1204 					}
1205 					else if (!strcmp (API->remote_info[k_data].dir, "/"))	/* One of the symbolic links in server */
1206 						snprintf (local_path, PATH_MAX, "%s/server/%s", GMT->session.USERDIR, API->remote_info[k_data].file);
1207 					else {
1208 						snprintf (local_path, PATH_MAX, "%s%s", GMT->session.USERDIR, API->remote_info[k_data].dir);
1209 						if (access (local_path, R_OK) && gmt_mkdir (local_path))	/* Have or just made a subdirectory under server */
1210 							GMT_Report (API, GMT_MSG_ERROR, "Unable to create GMT data directory : %s\n", local_path);
1211 						strcat (local_path, API->remote_info[k_data].file);
1212 					}
1213 				}
1214 				break;
1215 		}
1216 		if (jp2_file) gmt_M_str_free (jp2_file);
1217 		if (clean_file)	gmt_M_str_free (clean_file);
1218 		GMT_Report (API, GMT_MSG_DEBUG, "Get remote file %s and write to %s\n", remote_path, local_path);
1219 
1220 		return GMT_NOERROR;
1221 	}
1222 
1223 	/* URL files and queries and netcdf grids with directives all have characters like ? and = that
1224 	 * are not part of an actual file name (but is part of a query).  Need to deal with those complexities
1225 	 * here an keep track of if we got a query or a file request. */
1226 
1227 	is_url = (gmt_M_file_is_url (file));	/* A remote file or query given via an URL */
1228 	is_query = (gmt_M_file_is_query (file));	/* A remote file or query given via an URL */
1229 
1230 	if (strchr (file, '?') && (c = strstr (file, "=gd"))) {	/* Must be a netCDF sliced file to be read via GDAL so chop off the =gd?layer/variable specifications */
1231 		was = c[0]; c[0] = '\0';
1232 	}
1233 	else if ((c = strchr (file, '?')) && !strchr (file, '=')) {	/* Must be a netCDF sliced URL file so chop off the layer/variable specifications */
1234 		was = c[0]; c[0] = '\0';
1235 	}
1236 	else if (c == NULL && (c = strchr (file, '='))) {	/* If no ? then = means grid attributes (e.g., =bf) */
1237 		was = c[0]; c[0] = '\0';
1238 	}
1239 	else if (c) {	/* else we have both ? and = which means file is an URL query */
1240 		strncpy (remote_path, file, PATH_MAX-1);	/* Pass whatever we were given, no check possible */
1241 		was = c[0]; c[0] = '\0';
1242 	}
1243 
1244 	if (is_url) {	/* A remote file or query given via an URL never exists locally */
1245 		pos = gmtlib_get_pos_of_filename (file);	/* Start of file in URL (> 0) */
1246 		if (is_query) /* We have truncated off all the ?specifications part */
1247 			snprintf (local_path, PATH_MAX, "%s", &file[pos]);
1248 		else {	/* URL file, we have truncated off any netCDF directives */
1249 			strncpy (remote_path, file, PATH_MAX-1);
1250 			snprintf (local_path, PATH_MAX, "%s", &file[pos]);	/* Same. No directives when writing the file */
1251 		}
1252 		if (c) c[0] = was;
1253 		return GMT_NOERROR;
1254 	}
1255 
1256 	/* Looking for local files given a relative path - must search directories we are allowed.
1257 	 * Note: Any netCDF files with directives have had those chopped off earlier, so file is a valid name */
1258 
1259 	clean_file = gmt_get_filename (API, file, gmtlib_valid_filemodifiers (GMT));	/* Strip off any file modifier or netCDF directives */
1260 	if (gmt_getdatapath (GMT, clean_file, local_path, R_OK)) {	/* Found it */
1261 		/* Return full path */
1262 		if (c &&!is_query) {	/* We need to pass the ?var[]() or [id][+mods]stuff as part of the filename */
1263 			c[0] = was;
1264 			strcpy (local_path, file);
1265 		}
1266 		GMT_Report (API, GMT_MSG_DEBUG, "Replace file %s with path %s\n", file, local_path);
1267 		gmt_M_str_free (clean_file);
1268 		return GMT_NOERROR;
1269 	}
1270 
1271 	/* No luck whatsoever */
1272 
1273 	gmt_M_str_free (clean_file);
1274 
1275 	return GMT_FILE_NOT_FOUND;
1276 }
1277 
gmtlib_file_is_jpeg2000_tile(struct GMTAPI_CTRL * API,char * file)1278 int gmtlib_file_is_jpeg2000_tile (struct GMTAPI_CTRL *API, char *file) {
1279 	/* Detect if a file matches the name <path>/[N|S]yy[E|W]xxx.tag.jp2 (e.g., N22W160.earth_relief_01m_p.jp2) */
1280 	char *c, tmp[PATH_MAX] = {""};
1281 	if (file == NULL || file[0] == '\0') return GMT_NOTSET;	/* Bad argument */
1282 	if ((c = strrchr (file, '/')) == NULL)	/* Get place of the last slash */
1283 		sprintf (tmp, "@%s", file);	/* Now should have something like @N22W160.earth_relief_01m_p.jp2 */
1284 	else
1285 		sprintf (tmp, "@%s", &c[1]);	/* Now should have something like @N22W160.earth_relief_01m_p.jp2 */
1286 	return (gmt_file_is_a_tile (API, tmp, GMT_REMOTE_DIR));
1287 }
1288 
gmt_download_file(struct GMT_CTRL * GMT,const char * name,char * url,char * localfile,bool be_fussy)1289 int gmt_download_file (struct GMT_CTRL *GMT, const char *name, char *url, char *localfile, bool be_fussy) {
1290 	bool query = gmt_M_file_is_query (url), turn_ctrl_C_off = false;
1291 	int curl_err, error = 0;
1292 	size_t fsize;
1293 	CURL *Curl = NULL;
1294 	struct FtpFile urlfile = {NULL, NULL};
1295 	struct LOCFILE_FP *LF = NULL;
1296 	struct GMTAPI_CTRL *API = GMT->parent;
1297 
1298 	if (GMT->current.setting.auto_download == GMT_NO_DOWNLOAD) {  /* Not allowed to use remote copying */
1299 		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Remote download is currently deactivated\n");
1300 		return 1;
1301 	}
1302 	if (GMT->current.io.internet_error) return 1;   /* Not able to use remote copying in this session */
1303 
1304 	/* Check if the file is too big for our current limit */
1305 
1306 	if ((fsize = gmtremote_skip_large_files (GMT, url, GMT->current.setting.url_size_limit))) {
1307 		char *S = strdup (gmt_memory_use (fsize, 3));
1308 		GMT_Report (API, GMT_MSG_WARNING, "File %s skipped as size [%s] exceeds limit set by GMT_DATA_SERVER_LIMIT [%s]\n", name, S, gmt_memory_use (GMT->current.setting.url_size_limit, 0));
1309 		gmt_M_str_free (S);
1310 		return 1;
1311 	}
1312 
1313 	/* Here we will try to download a file */
1314 
1315 	/* Only make a lock if not a query */
1316 	if (!query && (LF = gmtremote_lock_on (GMT, (char *)name)) == NULL)
1317 		return 1;
1318 
1319 	/* If file locking held us up as another process was downloading the same file,
1320 	 * then that file should now be available.  So we check again if it is before proceeding */
1321 
1322 	if (!access (localfile, F_OK)) {	/* Yes it was! Undo lock and return no error */
1323 		if (!query)	/* Remove lock file after successful download (unless query) */
1324 			gmtremote_lock_off (GMT, &LF);
1325 		return GMT_NOERROR;
1326 	}
1327 
1328 	/* Initialize the curl session */
1329 	if ((Curl = gmtremote_setup_curl (API, url, localfile, &urlfile, 0)) == NULL)
1330 		goto unlocking2;
1331 
1332 	gmtremote_find_and_give_data_attribution (API, name);
1333 
1334 	GMT_Report (API, GMT_MSG_INFORMATION, "Downloading file %s ...\n", url);
1335 	gmtremote_turn_on_ctrl_C_check (localfile);
1336 	turn_ctrl_C_off = true;
1337 	if ((curl_err = curl_easy_perform (Curl))) {	/* Failed, give error message */
1338 		if (be_fussy || !(curl_err == CURLE_REMOTE_FILE_NOT_FOUND || curl_err == CURLE_HTTP_RETURNED_ERROR)) {	/* Unexpected failure - want to bitch about it */
1339 			GMT_Report (API, GMT_MSG_ERROR, "Libcurl Error: %s\n", curl_easy_strerror (curl_err));
1340 			GMT_Report (API, GMT_MSG_WARNING, "You can turn remote file download off by setting GMT_DATA_UPDATE_INTERVAL to \"off\"\n");
1341 			if (urlfile.fp != NULL) {
1342 				fclose (urlfile.fp);
1343 				urlfile.fp = NULL;
1344 			}
1345 			if (!access (localfile, F_OK) && gmt_remove_file (GMT, localfile))	/* Failed to clean up as well */
1346 				GMT_Report (API, GMT_MSG_WARNING, "Could not even remove file %s\n", localfile);
1347 		}
1348 		else if (curl_err == CURLE_COULDNT_CONNECT)
1349 			GMT->current.io.internet_error = true;	/* Prevent GMT from trying again in this session */
1350 	}
1351 	curl_easy_cleanup (Curl);
1352 	if (urlfile.fp) /* close the local file */
1353 		fclose (urlfile.fp);
1354 
1355 unlocking2:
1356 
1357 	if (!query)	/* Remove lock file after successful download (unless query) */
1358 		gmtremote_lock_off (GMT, &LF);
1359 
1360 	if (turn_ctrl_C_off) gmtremote_turn_off_ctrl_C_check ();
1361 
1362 	if (error == 0) error = gmtremote_convert_jp2_to_nc (API, localfile);
1363 
1364 	return (error);
1365 }
1366 
gmt_download_file_if_not_found(struct GMT_CTRL * GMT,const char * file,unsigned int mode)1367 unsigned int gmt_download_file_if_not_found (struct GMT_CTRL *GMT, const char *file, unsigned int mode) {
1368 	/* Downloads a file if not found locally.  Returns the position in file_name of the
1369  	 * start of the actual file (e.g., if given an URL). Values for mode:
1370  	 * 0 : Place file in the cache directory
1371 	 * 1 : Place file in user directory
1372 	 * 2 : Place file in local (current) directory
1373 	 * Add 4 if the file may not be found and we should not complain about this here.
1374 	 */
1375 	unsigned int pos = 0;
1376 	bool be_fussy;
1377 	char remote_path[PATH_MAX] = {""}, local_path[PATH_MAX] = {""};
1378 
1379 	if (gmt_M_file_is_memory (file)) return GMT_NOERROR;	/* Memory location always exists */
1380 	if (gmtlib_found_url_for_gdal ((char *)file)) return GMT_NOERROR;	/* /vis.../ files are read in GDAL */
1381 
1382 	be_fussy = ((mode & 4) == 0);	if (!be_fussy) mode -= 4;	/* Handle the optional 4 value */
1383 
1384 	if (file[0] == '@')	/* Make sure we have a refreshed server this session */
1385 		gmt_refresh_server (GMT->parent);
1386 
1387 	if (gmt_set_remote_and_local_filenames (GMT, file, local_path, remote_path, mode)) {
1388 		GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot find file %s\n", file);
1389 		return GMT_NOERROR;
1390 	}
1391 
1392 	if (remote_path[0]) {	/* Remote file given but not yet stored locally */
1393 		GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Download %s to %s\n", remote_path, local_path);
1394 		if (gmt_download_file (GMT, file, remote_path, local_path, be_fussy)) {
1395 			GMT_Report (GMT->parent, GMT_MSG_ERROR, "Unable to obtain remote file %s\n", file);
1396 			return GMT_NOERROR;
1397 		}
1398 	}
1399 	if (gmt_M_file_is_url (file))	/* A remote file or query given via an URL */
1400 		pos = gmtlib_get_pos_of_filename (file);	/* Start of file in URL (> 0) */
1401 	else if (strchr ("@=", file[0]))
1402 		pos = 1;
1403 
1404 	return (pos);
1405 }
1406 
1407 /* Support functions for tiled grids
1408  * gmtlib_file_is_tiled	Determine if a request was given for a tiled dataset.
1409  * gmt_file_is_tiled_list: Determine if file is a listfile with SRTM tile info
1410  * gmtlib_get_tile_list:	Convert -Rw/e/s/n into a file with a list of the tiles needed
1411  * gmtlib_assemble_tiles: Take the list, run grdblend to built the grid.
1412  *
1413  * The way the SRTM tiling works is that a user gives the virtual file name
1414  * @earth_relief_0[1|3]s or @srtm_relief_0[1|3]s. It is possible the user will append
1415  * _p for pixel registration but that is the only registration we have anyway.
1416  * If that is true (determined by gmtlib_remote_file_is_tiled) then we use the given
1417  * -Rw/e/s/n, the resolution given, and whether the ocean should be included (if the
1418  * srtm_relief_* name is given we only do land) and we create a list of all the
1419  * tiles that must be included to cover the region.  If ocean is requested we add in
1420  * the filler grid as well.  All these files are written in the form of remote
1421  * filenames (start with @).  This blend file is written and given a name that
1422  * is unique from the mask =tiled_<ID>_[G|P].######, where<ID> is the dataset iD
1423  * and G|P reflects if -R was a grid or plot region.
1424  * This filename is then used to replace the virtual grid we gave.  This happens
1425  * in gmt_init_module at the end of the function.  Then, when GMT_Read_Data is given
1426  * the tiled list file it knows what to do: Call gmtlib_assemble_tiles which will set
1427  * up and run a grdblend job that returns the desired custom-built grid.  Thus, when
1428  * grdblend starts accessing the files it finds that they are all remote files and
1429  * we will download a series of individual tiles (e.g., @N29W081.SRTMGL1.nc).
1430  */
1431 
gmtremote_is_directory(struct GMTAPI_CTRL * API,int k)1432 GMT_LOCAL bool gmtremote_is_directory (struct GMTAPI_CTRL *API, int k) {
1433 	/* If entry ends in / then it is a directory, else a file */
1434 	size_t len = strlen (API->remote_info[k].file);
1435 	if (len < 1) return false;
1436 	return (API->remote_info[k].file[len-1] == '/');
1437 }
1438 
gmtlib_remote_file_is_tiled(struct GMTAPI_CTRL * API,const char * file,unsigned int * mode)1439 int gmtlib_remote_file_is_tiled (struct GMTAPI_CTRL *API, const char *file, unsigned int *mode) {
1440 	/* Determine if file is referring to a tiled remote data set. */
1441 	int k_data;
1442 	if (!file || file[0] != '@') return GMT_NOTSET;	/* Not a remote file */
1443 	if (mode) *mode = 0;
1444 	if (strncmp (file, "@srtm_relief_0", 14U) == 0) {	/* This virtual tile set does not exist. It means earth_relief_0xs_g but do not add filler */
1445 		char tmpfile[GMT_LEN32] = {""};
1446 		sprintf (tmpfile, "@earth_relief_0%cs_g", file[14]);
1447 		if ((k_data = gmt_remote_dataset_id (API, tmpfile)) == GMT_NOTSET) return GMT_NOTSET;	/* Not a recognized remote dataset */
1448 		if (mode) *mode = GMT_SRTM_ONLY;
1449 		return (k_data);	/* Since we know earth_relief_01|d_g is a tiled directory */
1450 	}
1451 	if ((k_data = gmt_remote_dataset_id (API, file)) == GMT_NOTSET) return GMT_NOTSET;	/* Not a recognized remote dataset */
1452 	return (gmtremote_is_directory (API, k_data) ? k_data : GMT_NOTSET);	/* -1 for a regular, remote file, valid index for a directory */
1453 }
1454 
gmt_file_is_tiled_list(struct GMTAPI_CTRL * API,const char * file,int * ID,char * wetdry,char * region_type)1455 bool gmt_file_is_tiled_list (struct GMTAPI_CTRL *API, const char *file, int *ID, char *wetdry, char *region_type) {
1456 	char *c = NULL, dummy2, dummy3, *wet, *region;
1457 	int dummy1, *kval;
1458 	kval = (ID) ? ID : &dummy1;	/* Allow passing NULL if we don't care */
1459 	wet = (wetdry) ? wetdry : &dummy2;	/* Allow passing NULL if we don't care */
1460 	region = (region_type) ? region_type : &dummy3;	/* Allow passing NULL if we don't care */
1461 	*kval = GMT_NOTSET;
1462 	*wet = *region = 0;
1463 	if (file == NULL) return false;
1464 	if ((c = strstr (file, "=tiled_")) == NULL) return false;	/* Not that kind of file */
1465 	if (c[7] && sscanf (&c[7], "%d_%c%c", kval, region, wet) != 3) return false;	/* Not finding the id, land/ocean, and grid/plot markers */
1466 	if (strchr ("LOX", *wet) == NULL || strchr ("GP", *region) == NULL) return false;	/* Invalid characters for two keys */
1467 	if (*kval < 0 || (*kval) > API->n_remote_info) return false;	/* Outside recognized range of remote file IDs */
1468 	return true;	/* We got one */
1469 }
1470 
gmt_get_tile_id(struct GMTAPI_CTRL * API,char * file)1471 int gmt_get_tile_id (struct GMTAPI_CTRL *API, char *file) {
1472 	int k_data;
1473 	if (!gmt_file_is_tiled_list (API, file, &k_data, NULL, NULL)) return GMT_NOTSET;
1474 	return (k_data);
1475 }
1476 
gmt_file_is_a_tile(struct GMTAPI_CTRL * API,const char * infile,unsigned int where)1477 int gmt_file_is_a_tile (struct GMTAPI_CTRL *API, const char *infile, unsigned int where) {
1478 	/* Recognizes remote files like @N42E126.SRTMGL1.nc|jp2
1479 	 * where == 0 means local file (nc extension) and where = 1 means on server (jp2 extension) */
1480 	char *p = NULL, *file, tag[GMT_LEN64] = {""}, ext[GMT_LEN16] = {""};
1481 	int k_data, n;
1482 	size_t len = strlen (infile);
1483 	gmt_M_unused (API);
1484 	if (len < 12) return GMT_NOTSET;	/* Filename too short to hold a full tile name */
1485 	file = (char *)((infile[0] == '@') ? &infile[1] : infile);	/* Now, file starts at N|S */
1486 	if (strchr ("NS", file[0]) == 0) return GMT_NOTSET;	/* Does not start with N|S */
1487 	if (strchr ("EW", file[3]) == 0) return GMT_NOTSET;	/* Does not contain E|W */
1488 	if (!(isdigit (file[1]) && isdigit (file[2]))) return GMT_NOTSET;	/* No yy latitude */
1489 	if (!(isdigit (file[4]) && isdigit (file[5]) && isdigit (file[6]))) return GMT_NOTSET;	/* No xxx longitude */
1490 	if ((n = sscanf (file, "%*[^.].%[^.].%s", tag, ext)) != 2) return GMT_NOTSET;	/* Could not extract tag and extension */
1491 	if (where == GMT_REMOTE_DIR) { 	/* On the server the extension is jp2 */
1492 		if (strncmp (ext, GMT_TILE_EXTENSION_REMOTE, GMT_TILE_EXTENSION_REMOTE_LEN)) return GMT_NOTSET; /* Incorrect extension */
1493 	}
1494 	else if (where == GMT_LOCAL_DIR) {
1495 		if (strncmp (ext, GMT_TILE_EXTENSION_LOCAL, GMT_TILE_EXTENSION_LOCAL_LEN)) return GMT_NOTSET; /* Incorrect extension */
1496 	}
1497 	else {
1498 		GMT_Report (API, GMT_MSG_ERROR, "gmt_file_is_a_tile: Internal error - bad where assignment %d.\n", where);
1499 		return GMT_NOTSET;
1500 	}
1501 	if ((p = strstr (file, ".SRTMGL"))) /* Convert to the new tag for legacy SRTM tiles tag of SRTMGL[1|3] so it reflects the name of the dataset */
1502 		sprintf (tag, "earth_relief_0%cs_g", p[7]);	/* 7th char in p is the 1|3 resolution character */
1503 	k_data = gmt_remote_dataset_id (API, tag);
1504 	return (k_data);
1505 }
1506 
gmtremote_is_earth_dem(struct GMT_DATA_INFO * I)1507 GMT_LOCAL bool gmtremote_is_earth_dem (struct GMT_DATA_INFO *I) {
1508 	/* Returns true if this data set is one of the earth_relief clones that must share SRTM tiles with @earth_relief.
1509 	 * Should we add more such DEMs then just add more cases like the synbath test */
1510 	if (strstr (I->tag, "synbath") && (!strcmp (I->inc, "03s") || !strcmp (I->inc, "01s"))) return true;
1511 	return false;
1512 }
1513 
gmt_get_dataset_tiles(struct GMTAPI_CTRL * API,double wesn_in[],int k_data,unsigned int * n_tiles,bool * need_filler)1514 char ** gmt_get_dataset_tiles (struct GMTAPI_CTRL *API, double wesn_in[], int k_data, unsigned int *n_tiles, bool *need_filler) {
1515 	/* Return the full list of tiles for this tiled dataset. If need_filler is not NULL we return
1516 	 * true if some of the tiles inside the wesn do not exist based on the coverage map. */
1517 	bool partial_tile = false;
1518 	char **list = NULL, YS, XS, file[GMT_LEN64] = {""}, tag[GMT_LEN64] = {""};
1519 	int x, lon, clat, iw, ie, is, in, t_size;
1520 	uint64_t node, row, col;
1521 	unsigned int n_alloc = GMT_CHUNK, n = 0, n_missing = 0;
1522 	double wesn[4];
1523 	struct GMT_DATA_INFO *I = &API->remote_info[k_data];	/* Pointer to primary tiled dataset */
1524 	struct GMT_GRID *Coverage = NULL;
1525 
1526 	if (gmt_M_is_zero (I->tile_size)) return NULL;
1527 
1528 	strncpy (tag, I->tag, GMT_LEN64);	/* Initialize tag since it may change below */
1529 	if (strcmp (I->coverage, "-")) {	/* This primary tiled dataset has limited coverage as described by a named hit grid */
1530 		char coverage_file[GMT_LEN64] = {""};
1531 		sprintf (coverage_file, "@%s", I->coverage);	/* Prepend the remote flag since we may need to download the file */
1532 		if ((Coverage = GMT_Read_Data (API, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA, NULL, coverage_file, NULL)) == NULL) {
1533 			GMT_Report (API, GMT_MSG_ERROR, "gmt_get_dataset_tiles: Unable to obtain coverage grid %s of available tiles.\n", I->coverage);
1534 			API->error = GMT_RUNTIME_ERROR;
1535 			return NULL;
1536 		}
1537 		if (gmtremote_is_earth_dem (I))	/* Dataset shares SRTM1|3 tiles with @earth_relief */
1538 			sprintf (tag, "earth_relief_%s_%c", I->inc, I->reg);
1539 	}
1540 
1541 	if ((list = gmt_M_memory (API->GMT, NULL, n_alloc, char *)) == NULL) {
1542 		GMT_Report (API, GMT_MSG_ERROR, "gmt_get_dataset_tiles: Unable to allocate memory.\n");
1543 		API->error = GMT_RUNTIME_ERROR;
1544 		return NULL;
1545 	}
1546 
1547 	/* Preprocess the selected wesn to make it work better for a -180/180 window */
1548 	gmt_M_memcpy (wesn, wesn_in, 4U, double);
1549 	if (gmt_M_360_range (wesn[XLO], wesn[XHI])) wesn[XLO] = -180, wesn[XHI] = +180.0;
1550 	else if (wesn[XLO] < -180.0) wesn[XLO] += 360.0, wesn[XHI] += 360.0;
1551 	else if (wesn[XLO] > +180.0) wesn[XLO] -= 360.0, wesn[XHI] -= 360.0;
1552 	/* Get nearest whole multiple of tile size wesn boundary.  This ASSUMES all global grids are -Rd  */
1553 	/* Also, the srtm_tiles.nc grid is gridline-registered and we check if the node corresponding to
1554 	 * the lon/lat of the SW corner of a tile is 1 or 0 */
1555 	iw = (int)(-180 + floor ((wesn[XLO] + 180) / I->tile_size) * I->tile_size);
1556 	ie = (int)(-180 + ceil  ((wesn[XHI] + 180) / I->tile_size) * I->tile_size);
1557 	is = (int)( -90 + floor ((wesn[YLO] +  90) / I->tile_size) * I->tile_size);
1558 	in = (int)( -90 + ceil  ((wesn[YHI] +  90) / I->tile_size) * I->tile_size);
1559 	t_size = rint (I->tile_size);
1560 
1561 	for (clat = is; clat < in; clat += t_size) {	/* Loop over the rows of tiles */
1562 		if (Coverage && (clat < Coverage->header->wesn[YLO] || clat >= Coverage->header->wesn[YHI])) continue;	/* Outside Coverage band */
1563 		YS = (clat < 0) ? 'S' : 'N';
1564 		for (x = iw; x < ie; x += t_size) {	/* Loop over the columns of tiles */
1565 			lon = (x < 0) ? x + 360 : x;	/* Get longitude in 0-360 range */
1566 			if (Coverage) {	/* We will assume nothing about the west/east bounds of the coverage grid */
1567 				int clon = lon - 360;	/* Ensure we are far west */
1568 				while (clon < Coverage->header->wesn[XLO]) clon += 360;	/* Wind until past west */
1569 				if (clon > Coverage->header->wesn[XHI]) continue;	/* Outside Coverage band */
1570 				row  = gmt_M_grd_y_to_row (GMT, (double)clat, Coverage->header);
1571 				col  = gmt_M_grd_x_to_col (GMT, (double)clon, Coverage->header);
1572 				node = gmt_M_ijp (Coverage->header, row, col);
1573 				if (Coverage->data[node] == GMT_NO_TILE) {	/* No such tile exists */
1574 					n_missing++;	/* Add up missing tiles */
1575 					continue;		/* Go to next tile */
1576 				}
1577 				else if (Coverage->data[node] == GMT_PARTIAL_TILE)	/* Not missing, but still need ocean filler for partial tile */
1578 					partial_tile = true;	/* Note: We also get here with GMT < 6.3 so that @earth_relief_15s is always considered */
1579 			}
1580 			lon = (x >= 180) ? x - 360 : x;	/* Need longitudes 0-179 for E and 1-180 for W */
1581 			XS = (lon < 0) ? 'W' : 'E';
1582 			/* Write remote tile name to list */
1583 			if (n >= n_alloc) {
1584 				n_alloc <<= 1;
1585 				if ((list = gmt_M_memory (API->GMT, list, n_alloc, char *)) == NULL) {
1586 					GMT_Report (API, GMT_MSG_ERROR, "gmt_get_dataset_tiles: Unable to reallocate memory.\n");
1587 					API->error = GMT_RUNTIME_ERROR;
1588 					return NULL;
1589 				}
1590 			}
1591 			sprintf (file, "@%c%2.2d%c%3.3d.%s.%s", YS, abs(clat), XS, abs(lon), tag, GMT_TILE_EXTENSION_LOCAL);
1592 			list[n++] = strdup (file);
1593 		}
1594 	}
1595 	if (Coverage && GMT_Destroy_Data (API, &Coverage) != GMT_NOERROR) {
1596 		GMT_Report (API, GMT_MSG_WARNING, "gmtlib_get_tile_list: Unable to destroy coverage grid.\n");
1597 	}
1598 
1599 	if (need_filler) *need_filler = (partial_tile || n_missing > 0);	/* Incomplete coverage of this data set within wesn */
1600 
1601 	*n_tiles = n;
1602 	if (n == 0) {	/* Nutin' */
1603 		gmt_M_free (API->GMT, list);
1604 		GMT_Report (API, GMT_MSG_WARNING, "gmt_get_dataset_tiles: No %s tiles available for your region.\n", I->tag);
1605 		return NULL;
1606 	}
1607 
1608 	if ((list = gmt_M_memory (API->GMT, list, n, char *)) == NULL) {
1609 		GMT_Report (API, GMT_MSG_ERROR, "gmt_get_dataset_tiles: Unable to finalize memory.\n");
1610 		API->error = GMT_RUNTIME_ERROR;
1611 		return NULL;
1612 	}
1613 
1614 	return (list);
1615 }
1616 
gmtlib_get_tile_list(struct GMTAPI_CTRL * API,double wesn[],int k_data,bool plot_region,unsigned int srtm_flag)1617 char *gmtlib_get_tile_list (struct GMTAPI_CTRL *API, double wesn[], int k_data, bool plot_region, unsigned int srtm_flag) {
1618 	/* Builds a list of the tiles to download for the chosen region, dataset and resolution.
1619 	 * Uses the optional tile information grid to know if a particular tile exists. */
1620 	bool need_filler;
1621 	char tile_list[PATH_MAX] = {""}, stem[GMT_LEN32] = {""}, *file = NULL, **tile = NULL, datatype[3] = {'L', 'O', 'X'}, regtype[2] = {'G', 'P'};
1622 	int k_filler = GMT_NOTSET;
1623 	unsigned int k, n_tiles = 0, ocean = (srtm_flag) ? 0 : 2;
1624 	FILE *fp = NULL;
1625 	struct GMT_DATA_INFO *Ip = &API->remote_info[k_data], *Is = NULL;	/* Pointer to primary tiled dataset */
1626 
1627 	/* See if we want a background filler - this is most likely when using SRTM tiles and a 15s background ocean */
1628 	if (strcmp (Ip->filler, "-") && srtm_flag == 0) {	/* Want background filler, except special case when srtm_relief is the given dataset name (srtm_flag == 1) */
1629 		if ((k_filler = gmt_remote_dataset_id (API, Ip->filler)) == GMT_NOTSET) {
1630 			GMT_Report (API, GMT_MSG_ERROR, "gmtlib_get_tile_list: Internal error - Filler grid %s is not a recognized remote data set.\n", Ip->filler);
1631 			return NULL;
1632 		}
1633 		Is = &API->remote_info[k_filler];	/* Pointer to secondary tiled dataset */
1634 		ocean = (strcmp (Is->inc, "15s") == 0);
1635 	}
1636 
1637 	/* Create temporary filename for list of tiles */
1638 
1639 	snprintf (stem, GMT_LEN32, "=tiled_%d_%c%c", k_data, regtype[plot_region], datatype[ocean]);
1640 	if ((fp = gmt_create_tempfile (API, stem, NULL, tile_list)) == NULL) {	/* Not good... */
1641 		GMT_Report (API, GMT_MSG_ERROR, "gmtlib_get_tile_list: Unable to create list of tiles from template: %s.\n", tile_list);
1642 		return NULL;
1643 	}
1644 	file = tile_list;	/* Pointer to the buffer with the name */
1645 
1646 	/* Get the primary tiles and determine if the filler grid is needed */
1647 	tile = gmt_get_dataset_tiles (API, wesn, k_data, &n_tiles, &need_filler);
1648 
1649 	/* Write primary tiles to list file */
1650 	for (k = 0; k < n_tiles; k++)
1651 		fprintf (fp, "%s\n", tile[k]);
1652 
1653 	gmt_free_list (API->GMT, tile, n_tiles);	/* Free the primary tile list */
1654 	if (k_filler != GMT_NOTSET) {	/* Want the secondary tiles */
1655 		if (need_filler && (tile = gmt_get_dataset_tiles (API, wesn, k_filler, &n_tiles, NULL))) {
1656 			/* Write secondary tiles to list file */
1657 			for (k = 0; k < n_tiles; k++)
1658 				fprintf (fp, "%s\n", tile[k]);
1659 			gmt_free_list (API->GMT, tile, n_tiles);	/* Free the secondary tile list */
1660 		}
1661 		if (Ip->d_inc < Is->d_inc) {
1662 			/* If selected dataset has smaller increment that the filler grid then we adjust -R to be  a multiple of the larger spacing. */
1663 			/* Enforce multiple of tile grid resolution in wesn so requested region is in phase with tiles and at least covers the given
1664 			 * region. The GMT_CONV8_LIMIT is there to ensure we won't round an almost exact x/dx away from the truth. */
1665 			wesn[XLO] = floor ((wesn[XLO] / Is->d_inc) + GMT_CONV8_LIMIT) * Is->d_inc;
1666 			wesn[XHI] = ceil  ((wesn[XHI] / Is->d_inc) - GMT_CONV8_LIMIT) * Is->d_inc;
1667 			wesn[YLO] = floor ((wesn[YLO] / Is->d_inc) + GMT_CONV8_LIMIT) * Is->d_inc;
1668 			wesn[YHI] = ceil  ((wesn[YHI] / Is->d_inc) - GMT_CONV8_LIMIT) * Is->d_inc;
1669 		}
1670 	}
1671 	fclose (fp);
1672 
1673 	gmt_M_memcpy (API->tile_wesn, wesn, 4, double);	/* Retain this knowledge in case it was obtained via map_setup for an oblique area */
1674 
1675 	return (strdup (file));
1676 }
1677 
gmtlib_assemble_tiles(struct GMTAPI_CTRL * API,double * region,char * file)1678 struct GMT_GRID *gmtlib_assemble_tiles (struct GMTAPI_CTRL *API, double *region, char *file) {
1679 	/* Get here if file is a =tiled_<id>_G|P.xxxxxx file.  Need to do:
1680 	 * Set up a grdblend command and return the assembled grid
1681 	 */
1682 	int k_data, v_level = API->verbose;
1683 	struct GMT_GRID *G = NULL;
1684 	double *wesn = (region) ? region : API->tile_wesn;	/* Default to -R */
1685 	char grid[GMT_VF_LEN] = {""}, cmd[GMT_LEN256] = {""}, code = 0;;
1686 	struct GMT_GRID_HEADER_HIDDEN *HH = NULL;
1687 
1688 	(void) gmt_file_is_tiled_list (API, file, NULL, &code, NULL);	/* Just get the code*/
1689 
1690 	if ((k_data = gmt_get_tile_id (API, file)) == GMT_NOTSET) {
1691 		GMT_Report (API, GMT_MSG_ERROR, "Internal error: Non-recognized tiled ID embedded in file %s\n", file);
1692 		return NULL;
1693 	}
1694 
1695 	if (API->verbose == GMT_MSG_WARNING) API->verbose = GMT_MSG_ERROR;	/* Drop from warnings to errors only when calling grdblend to avoid annoying messages about phase/shift from SRTM01|3 and 15s */
1696 	GMT_Open_VirtualFile (API, GMT_IS_GRID, GMT_IS_SURFACE, GMT_OUT|GMT_IS_REFERENCE, NULL, grid);
1697 	/* Pass -N0 so that missing tiles (oceans) yield z = 0 and not NaN, and -Co+n to override using negative earth_relief_15s values */
1698 	snprintf (cmd, GMT_LEN256, "%s -R%.16g/%.16g/%.16g/%.16g -I%s -r%c -G%s -fg -Co+n", file, wesn[XLO], wesn[XHI], wesn[YLO], wesn[YHI], API->remote_info[k_data].inc, API->remote_info[k_data].reg, grid);
1699 	if (code != 'X') strcat (cmd, " -N0 -Ve");	/* If ocean/land, set empty nodes to 0, else NaN. Also turn of warnings since mixing pixel and gridline grids, possibly */
1700 	if (GMT_Call_Module (API, "grdblend", GMT_MODULE_CMD, cmd) != GMT_NOERROR) {
1701 		API->verbose = v_level;
1702 		GMT_Report (API, GMT_MSG_ERROR, "ERROR - Unable to produce blended grid from %s\n", file);
1703 		return NULL;
1704 	}
1705 	if ((G = GMT_Read_VirtualFile (API, grid)) == NULL) {	/* Load in the resampled grid */
1706 		API->verbose = v_level;
1707 		GMT_Report (API, GMT_MSG_ERROR, "ERROR - Unable to receive blended grid from grdblend\n");
1708 		return NULL;
1709 	}
1710 
1711 	API->verbose = v_level;
1712 	HH = gmt_get_H_hidden (G->header);
1713 	HH->orig_datatype = GMT_SHORT;	/* Since we know this */
1714 	return (G);
1715 }
1716 
gmt_download_tiles(struct GMTAPI_CTRL * API,char * list,unsigned int mode)1717 int gmt_download_tiles (struct GMTAPI_CTRL *API, char *list, unsigned int mode) {
1718 	/* Download all tiles not already here given by the list */
1719 	uint64_t n, k;
1720 	char **file = NULL;
1721 
1722 	if (!gmt_file_is_tiled_list (API, list, NULL, NULL, NULL)) return GMT_RUNTIME_ERROR;
1723 	if ((n = gmt_read_list (API->GMT, list, &file)) == 0) return GMT_RUNTIME_ERROR;
1724 	for (k = 0; k < n; k++) {
1725 		gmt_download_file_if_not_found (API->GMT, file[k], mode);
1726 	}
1727 	gmt_free_list (API->GMT, file, n);
1728 	return GMT_NOERROR;
1729 }
1730 
gmt_dataserver_url(struct GMTAPI_CTRL * API)1731 char *gmt_dataserver_url (struct GMTAPI_CTRL *API) {
1732 	/* Build the full URL to the currently selected data server */
1733 	static char URL[GMT_LEN256] = {""}, *link = URL;
1734 	if (strncmp (API->GMT->session.DATASERVER, "http", 4U)) {	/* Not an URL so must assume it is the country/unit name, e.g., oceania */
1735 		/* We make this part case insensitive since all official GMT servers are lower-case */
1736 		char name[GMT_LEN64] = {""};
1737 		strncpy (name, API->GMT->session.DATASERVER, GMT_LEN64-1);
1738 		gmt_str_tolower (name);
1739 		snprintf (URL, GMT_LEN256-1, "http://%s.generic-mapping-tools.org", name);
1740 	}
1741 	else	/* Must use the URL as is */
1742 		snprintf (URL, GMT_LEN256-1, "%s", API->GMT->session.DATASERVER);
1743 	return (link);
1744 }
1745