1 
2 /*
3  * Unix micro-cmm to manage X11 display
4  * calibration and profile loading.
5  */
6 
7 /*************************************************************************
8  Copyright 2008 Graeme W. Gill
9 
10  Permission is hereby granted, free of charge, to any person obtaining a copy
11  of this software and associated documentation files (the "Software"), to deal
12  in the Software without restriction, including without limitation the rights
13  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14  copies of the Software, and to permit persons to whom the Software is
15  furnished to do so, subject to the following conditions:
16 
17  The above copyright notice and this permission notice shall be included in
18  all copies or substantial portions of the Software.
19 
20  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26  THE SOFTWARE.
27 
28  *************************************************************************/
29 
30 /* We use libjcnf to store the ICC profile association with particular displays */
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <stdarg.h>
35 #include <string.h>
36 #include <math.h>
37 #include <time.h>
38 #include <signal.h>
39 #ifndef NT
40 # include <unistd.h>
41 #endif
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include "icc.h"
45 #include "xdg_bds.h"
46 #include "jcnf.h"
47 #include "ucmm.h"
48 
49 #undef DEBUG
50 
51 #define CONFIG_FILE "color.jcnf"
52 #define PROFILE_DIR "color/icc/devices/display"
53 
54 #ifdef DEBUG
55 # define errout stderr
56 # define debug(xx)	fprintf(errout, xx )
57 # define debug2(xx)	fprintf xx
58 #else
59 # define debug(xx)
60 # define debug2(xx)
61 #endif
62 
63 static unsigned int fnv_32_buf(void *buf, size_t len);
64 
65 #ifdef NT
66 
67 /* Given the path to a file, ensure that all the parent directories */
68 /* are created. return nz on error */
mkdirs(char * path)69 static int mkdirs(char *path) {
70 	struct _stat sbuf;
71 	char *pp = path;
72 
73 	if (*pp != '\000'		/* Skip drive number */
74 		&& ((*pp >= 'a' && *pp <= 'z') || (*pp >= 'A' && *pp <= 'Z'))
75 	    && pp[1] == ':')
76 		pp += 2;
77 	if (*pp == '/')
78 		pp++;			/* Skip root directory */
79 	for (;pp != NULL && *pp != '\000';) {
80 		if ((pp = strchr(pp, '/')) != NULL) {
81 			*pp = '\000';
82 			if (_stat(path,&sbuf) != 0)
83 			{
84 				if (_mkdir(path) != 0)
85 					return 1;
86 			}
87 			*pp = '/';
88 			pp++;
89 		}
90 	}
91 	return 0;
92 }
93 
94 #else
95 
96 /* Given the path to a file, ensure that all the parent directories */
97 /* are created. return nz on error */
mkpdirs(char * path)98 static int mkpdirs(char *path) {
99 	struct stat sbuf;
100 	char *pp = path;
101 	mode_t mode = 0700;		/* Default directory mode */
102 
103 	if (*pp == '/')
104 		pp++;			/* Skip root directory */
105 	for (;pp != NULL && *pp != '\000';) {
106 		if ((pp = strchr(pp, '/')) != NULL) {
107 			*pp = '\000';
108 			if (stat(path,&sbuf) != 0) {
109 				if (mkdir(path, mode) != 0)
110 					return 1;
111 			} else
112 				mode = sbuf.st_mode;
113 			*pp = '/';
114 			pp++;
115 		}
116 	}
117 	return 0;
118 }
119 #endif /* !NT */
120 
121 /* Given a block of binary, convert it to upper case hexadecimal, */
122 /* with a 0x prefix. Free the buffer returned. */
123 /* Return NULL on error */
buf2hex(unsigned char * buf,int len)124 static char *buf2hex(unsigned char *buf, int len) {
125 	char *s;
126 	int i;
127 
128 	char hex[17] = "0123456789ABCDEF";
129 
130 	if ((s = malloc(len * 2 + 3)) == NULL)
131 		return NULL;
132 
133 	s[0] = '0';
134 	s[1] = 'x';
135 
136 	for (i = 0; i < len; i++) {
137 		s[2 + i * 2 + 0] = hex[(buf[i] >> 4) & 0xf];
138 		s[2 + i * 2 + 1] = hex[buf[i] & 0xf];
139 	}
140 	s[2 + i * 2 + 0] = '\000';
141 
142 	return s;
143 }
144 
145 
146 /* Install a profile for a given monitor */
147 /* Either EDID or display_name may be NULL, but not both. */
148 /* Any existing association is overwritten. Installed profiles */
149 /* are not deleted. */
ucmm_install_monitor_profile(ucmm_scope scope,unsigned char * edid,int edid_len,char * display_name,char * profile)150 ucmm_error ucmm_install_monitor_profile(
151 	ucmm_scope scope,		/* Scope of instalation */
152 	unsigned char *edid,	/* Primary device identifier, NULL if none. */
153 	int edid_len,			/* Length of edid data */
154 	char *display_name,		/* Fall back device association, */
155 	                        /* the X11 display name */
156 	char *profile			/* Path to profile to be installed. */
157 ) {
158 	char *config_file = CONFIG_FILE;
159 	char *profile_dir = PROFILE_DIR;
160 	char *conf_name = NULL;		/* Configuration path to use */
161 	char *data_name = NULL;		/* Data path to use */
162 	char *dprof = NULL;			/* Destination for profile */
163 	unsigned int edid_hash = 0;
164 
165 	if (edid != NULL)
166 		edid_hash = fnv_32_buf(edid, edid_len);
167 
168 	debug2((errout,"ucmm_install_monitor_profile called with profile '%s', edid 0x%x, disp '%s'\n",profile,edid_hash,display_name));
169 
170 	/* Verify that we've been given a suitable ICC profile */
171 	/* And read it into a memory buffer */
172 	{
173 		icmFile *fp;
174 		icc *icco;
175 
176 		if ((fp = new_icmFileStd_name(profile,"r")) == NULL) {
177 			debug2((errout,"Unable to ope file '%s'\n",profile));
178 			return ucmm_invalid_profile;
179 		}
180 
181 		if ((icco = new_icc()) == NULL) {
182 			debug2((errout,"new_icc() failed\n"));
183 			fp->del(fp);
184 			return ucmm_invalid_profile;
185 		}
186 
187 		if (icco->read(icco,fp,0) != 0) {
188 			debug2((errout,"icc read of '%s' failed\n",profile));
189 			icco->del(icco);
190 			fp->del(fp);
191 			return ucmm_invalid_profile;
192 		}
193 
194 		if (icco->header->deviceClass != icSigDisplayClass
195 		 || icco->header->colorSpace != icSigRgbData) {
196 			debug2((errout,"profile '%s' isn't an RGB display profile\n",profile));
197 			icco->del(icco);
198 			fp->del(fp);
199 			return ucmm_invalid_profile;
200 		}
201 		icco->del(icco);
202 		fp->del(fp);
203 	}
204 
205 	debug2((errout,"verified profile OK\n"));
206 
207 	/* Locate the directories where the config file is, */
208 	/* and where we should copy the profile to. */
209 	{
210 		int npaths;
211 		xdg_error er;
212 		char *data_pathfile;		/* Path & name of destination */
213 		char **paths;
214 		char *tt;
215 
216 		if ((npaths = xdg_bds(&er, &paths, xdg_conf, xdg_write,
217 		                     scope == ucmm_local_system ? xdg_local : xdg_user,
218 		                     config_file)) == 0) {
219 			return ucmm_open_config;
220 		}
221 		if ((conf_name = strdup(paths[0])) == NULL) {
222 			xdg_free(paths, npaths);
223 			return ucmm_resource;
224 		}
225 		xdg_free(paths, npaths);
226 
227 		/* Combined sub-path and profile name */
228 		if ((data_pathfile = malloc(strlen(profile_dir) + 1 + strlen(profile) + 1)) == NULL)
229 			return ucmm_resource;
230 		strcpy(data_pathfile, profile_dir);
231 
232 		if (strlen(data_pathfile) > 1 && data_pathfile[strlen(data_pathfile)-1] != '/')
233 			strcat(data_pathfile, "/");
234 
235 		if ((tt = strrchr(profile, '/')) != NULL)	/* Get base name of profile */
236 			tt++;
237 		else
238 			tt = profile;
239 		strcat(data_pathfile, tt);
240 
241 		if ((npaths = xdg_bds(&er, &paths, xdg_conf, xdg_write,
242 		                     scope == ucmm_local_system ? xdg_local : xdg_user,
243 		                     data_pathfile)) == 0) {
244 			free(data_pathfile);
245 			free(conf_name);
246 			return ucmm_open_config;
247 		}
248 		free(data_pathfile);
249 		if ((data_name = strdup(paths[0])) == NULL) {
250 			free(conf_name);
251 			xdg_free(paths, npaths);
252 			return ucmm_resource;
253 		}
254 		xdg_free(paths, npaths);
255 	}
256 
257 	debug2((errout,"config file = '%s'\n",conf_name));
258 	debug2((errout,"data file = '%s'\n",data_name));
259 
260 	/* Copy the profile to the destination */
261 	{
262 		FILE *fp;
263 		unsigned char *pdata;
264 		unsigned long psize;
265 
266 		/* Read in the ICC profile, then set the X11 atom value */
267 #if defined(O_BINARY) || defined(_O_BINARY)
268 		if ((fp = fopen(profile,"rb")) == NULL)
269 #else
270 		if ((fp = fopen(profile,"r")) == NULL)
271 #endif
272 		{
273 			debug2((errout,"Can't open file '%s'\n",profile));
274 			free(conf_name);
275 			free(data_name);
276 			return ucmm_profile_copy;
277 		}
278 
279 		/* Figure out how big it is */
280 		if (fseek(fp, 0, SEEK_END)) {
281 			debug2((errout,"Seek '%s' to EOF failed\n",profile));
282 			free(conf_name);
283 			free(data_name);
284 			return ucmm_profile_copy;
285 		}
286 		psize = (unsigned long)ftell(fp);
287 
288 		if (fseek(fp, 0, SEEK_SET)) {
289 			debug2((errout,"Seek '%s' to SOF failed\n",profile));
290 			free(conf_name);
291 			free(data_name);
292 			return ucmm_profile_copy;
293 		}
294 
295 		if ((pdata = (unsigned char *)malloc(psize)) == NULL) {
296 			debug2((errout,"Failed to allocate buffer for profile '%s'\n",profile));
297 			free(conf_name);
298 			free(data_name);
299 			return ucmm_profile_copy;
300 		}
301 
302 		if (fread(pdata, 1, psize, fp) != psize) {
303 			debug2((errout,"Failed to read profile '%s' into buffer\n",profile));
304 			free(conf_name);
305 			free(data_name);
306 			return ucmm_profile_copy;
307 		}
308 
309 		fclose(fp);
310 
311 		/* Write the profile to its location */
312 		if (mkpdirs(data_name)) {
313 			debug2((errout,"Can't create directories for file '%s'\n",data_name));
314 			free(conf_name);
315 			free(data_name);
316 			return ucmm_profile_copy;
317 		}
318 #if defined(O_BINARY) || defined(_O_BINARY)
319 		if ((fp = fopen(data_name,"wb")) == NULL)
320 #else
321 		if ((fp = fopen(data_name,"w")) == NULL)
322 #endif
323 		{
324 			debug2((errout,"Can't create file '%s'\n",data_name));
325 			free(conf_name);
326 			free(data_name);
327 			return ucmm_profile_copy;
328 		}
329 
330 		if (fwrite(pdata, 1, psize, fp) != psize) {
331 			debug2((errout,"Failed to write profile '%s' into buffer\n",data_name));
332 			free(conf_name);
333 			free(data_name);
334 			return ucmm_profile_copy;
335 		}
336 
337 		if (fclose(fp) != 0) {
338 			debug2((errout,"Failed to close profile '%s' into buffer\n",data_name));
339 			free(conf_name);
340 			free(data_name);
341 			return ucmm_profile_copy;
342 		}
343 	}
344 
345 	debug2((errout,"profile copied OK\n"));
346 
347 	/* Update the config file */
348 	{
349 		jc_error ev;
350 		jcnf *jc;
351 		char keyn1[100];
352 		char keyn2[100];
353 		char *mname;			/* Name of key to match to */
354 		char *mval;				/* Value to match */
355 		int ix = 0;
356 		int recno = -1;		/* Number of the last record read */
357 
358 		/* Open the configuration file for modification */
359 		if (mkpdirs(conf_name)) {
360 			debug2((errout,"Can't create directories for file '%s'\n",conf_name));
361 			free(conf_name);
362 			free(data_name);
363 			return ucmm_open_config;
364 		}
365 
366 		if ((jc = new_jcnf(&ev, conf_name, jc_modify, jc_create)) == NULL) {
367 			debug2((errout,"new_jcnf '%s' failed with error %d\n",conf_name,ev));
368 			free(conf_name);
369 			free(data_name);
370 			return ucmm_open_config;
371 		}
372 
373 		/* if EDID supplied, Locate a matching EDID */
374 		if (edid != NULL) {
375 			mname = "EDID";
376 			if ((mval = buf2hex(edid, edid_len)) == NULL) {
377 				jc->del(jc);
378 				free(conf_name);
379 				free(data_name);
380 				return ucmm_resource;
381 			}
382 
383 		/* Else fall back to X11 display name and screen */
384 		} else {
385 			if (display_name == NULL) {
386 				jc->del(jc);
387 				free(conf_name);
388 				free(data_name);
389 				return ucmm_no_edid_or_display;
390 			}
391 			mname = "NAME";
392 			if ((mval = strdup(display_name)) == NULL) {
393 				jc->del(jc);
394 				free(conf_name);
395 				free(data_name);
396 				return ucmm_resource;
397 			}
398 		}
399 
400 		debug2((errout,"Searching for %s = '%s'\n",mname,mval));
401 		for (;;ix++) {
402 			char *key, *pp;
403 			jc_type type;
404 			unsigned char *data;
405 			size_t dataSize;
406 			int ii;
407 
408 			if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok
409 			 || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) {
410 				if (ev == jc_ix_oorange) {
411 					break;
412 				}
413 				debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
414 				free(mval);
415 				jc->del(jc);
416 				free(conf_name);
417 				free(data_name);
418 				return ucmm_open_config;
419 			}
420 
421 			if ((pp = jc_get_nth_elem(key, 2)) == NULL) {
422 				continue;
423 			}
424 			if ((ii = atoi(pp)) == 0) {
425 				free(pp);
426 				continue;
427 			}
428 			if (ii > recno)		/* Track biggest, so we know what to create next */
429 				recno = ii;
430 			if ((pp = jc_get_nth_elem(key, 3)) != NULL && strcmp(pp, mname) == 0 && type == jc_string && strcmp(data, mval) == 0) {
431 				/* Found matching record */
432 				free(pp);
433 				break;
434 			}
435 			if (pp != NULL)
436 				free(pp);
437 		}
438 
439 		/* Create a new record */
440 		if (ev == jc_ix_oorange) {
441 			recno++;				/* Make it the next index */
442 			if (recno <= 0)
443 				recno = 1;
444 			debug2((errout, "Adding a new record %d\n",recno));
445 		} else {
446 			debug2((errout, "Replacing record %d\n",recno));
447 		}
448 
449 		/* Write the record */
450 		sprintf(keyn1, "devices/display/%d/%s", recno, mname);
451 		sprintf(keyn2, "devices/display/%d/ICC_PROFILE", recno);
452 		if ((ev = jc->set_key(jc, -1, keyn1, jc_string, mval, strlen(mval)+1, NULL)) != jc_ok
453 		 || (ev = jc->set_key(jc, -1, keyn2, jc_string, data_name, strlen(data_name)+1, NULL)) != jc_ok) {
454 			debug2((errout,"jcnf set_key failed with error %d\n",ev));
455 			free(mval);
456 			jc->del(jc);
457 			free(conf_name);
458 			free(data_name);
459 			return ucmm_set_config;
460 		}
461 		free(mval);
462 
463 		/* write to record the EDID or display name and the profile path */
464 		if ((ev = jc->update(jc)) != 0) {
465 			debug2((errout,"jcnf write to '%s' failed with error %d\n",conf_name,ev));
466 			jc->del(jc);
467 			free(conf_name);
468 			free(data_name);
469 			return ucmm_save_config;
470 		}
471 		debug2((errout,"Updated config file '%s'\n",conf_name));
472 
473 		/* We're done with this */
474 		jc->del(jc);
475 		free(conf_name);
476 		free(data_name);
477 	}
478 	debug2((errout,"ucmm done profile install\n"));
479 	return ucmm_ok;
480 }
481 
482 /* Un-install a profile for a given monitor. */
483 /* Either EDID or display_name may be NULL, but not both. */
484 /* The monitor is left with no profile association. If a profile */
485 /* name is provided and matches the one that was associated with */
486 /* the monitor, and has no other association, then it will be deleted */
487 /* from the data directory. */
488 /* Return an error code */
ucmm_uninstall_monitor_profile(ucmm_scope scope,unsigned char * edid,int edid_len,char * display_name,char * profile)489 ucmm_error ucmm_uninstall_monitor_profile(
490 	ucmm_scope scope,		/* Scope of instalation */
491 	unsigned char *edid,	/* Primary device identifier, NULL if none. */
492 	int edid_len,			/* Length of edid data */
493 	char *display_name,		/* Fall back device association, */
494 	char *profile			/* Base name of profile to be deleted. NULL if not to be deleted. */
495 ) {
496 	char *config_file = CONFIG_FILE;
497 	char *profile_dir = PROFILE_DIR;
498 	char *conf_name = NULL;		/* Configuration path to use */
499 	char *data_name = NULL;		/* Data path to use */
500 	char *dprof = NULL;			/* Destination for profile */
501 	unsigned int edid_hash = 0;
502 
503 	if (edid != NULL)
504 		edid_hash = fnv_32_buf(edid, edid_len);
505 
506 	debug2((errout,"ucmm_uninstall_monitor_profile called with profile '%s', edid 0x%x, disp '%s'\n",profile,edid_hash,display_name));
507 
508 	/* Locate the directories where the config file is, */
509 	/* and where the profile should be too. */
510 	{
511 		int npaths;
512 		xdg_error er;
513 		char *data_pathfile;		/* Path & name of destination */
514 		char **paths;
515 		char *tt;
516 
517 		if ((npaths = xdg_bds(&er, &paths, xdg_conf, xdg_read,
518 		                     scope == ucmm_local_system ? xdg_local : xdg_user,
519 		                     config_file)) == 0) {
520 			return ucmm_open_config;
521 		}
522 		if ((conf_name = strdup(paths[0])) == NULL) {
523 			xdg_free(paths, npaths);
524 			return ucmm_resource;
525 		}
526 		xdg_free(paths, npaths);
527 
528 		if (profile != NULL) {
529 			/* Combined sub-path and profile name */
530 			if ((data_pathfile = malloc(strlen(profile_dir) + 1 + strlen(profile) + 1)) == NULL)
531 				return ucmm_resource;
532 			strcpy(data_pathfile, profile_dir);
533 
534 			if (strlen(data_pathfile) > 1 && data_pathfile[strlen(data_pathfile)-1] != '/')
535 				strcat(data_pathfile, "/");
536 
537 			if ((tt = strrchr(profile, '/')) != NULL)	/* Get base name of profile */
538 				tt++;
539 			else
540 				tt = profile;
541 			strcat(data_pathfile, tt);
542 
543 			if ((npaths = xdg_bds(&er, &paths, xdg_conf, xdg_read,
544 			                     scope == ucmm_local_system ? xdg_local : xdg_user,
545 			                     data_pathfile)) == 0) {
546 				free(data_pathfile);
547 				free(conf_name);
548 				return ucmm_open_config;
549 			}
550 			free(data_pathfile);
551 			if ((data_name = strdup(paths[0])) == NULL) {
552 				free(conf_name);
553 				xdg_free(paths, npaths);
554 				return ucmm_resource;
555 			}
556 			xdg_free(paths, npaths);
557 		}
558 	}
559 
560 	debug2((errout,"config file = '%s'\n",conf_name));
561 	if (data_name != NULL)
562 		debug2((errout,"data file = '%s'\n",data_name));
563 
564 	/* Get the config file */
565 	{
566 		jc_error ev;
567 		jcnf *jc;
568 		char keyn1[100];
569 		char *mname;			/* Name of key to match to */
570 		char *mval;				/* Value to match */
571 		int ix;
572 		int recno = -1;		/* Number of the last record read */
573 
574 		/* Open the configuration file for modification */
575 		if (mkpdirs(conf_name)) {
576 			debug2((errout,"Can't create directories for file '%s'\n",conf_name));
577 			free(conf_name);
578 			if (data_name != NULL)
579 				free(data_name);
580 			return ucmm_open_config;
581 		}
582 
583 		if ((jc = new_jcnf(&ev, conf_name, jc_modify, jc_create)) == NULL) {
584 			debug2((errout,"new_jcnf '%s' failed with error %d\n",conf_name,ev));
585 			free(conf_name);
586 			if (data_name != NULL)
587 				free(data_name);
588 			return ucmm_open_config;
589 		}
590 
591 		/* if EDID supplied, Locate a matching EDID */
592 		if (edid != NULL) {
593 			mname = "EDID";
594 			if ((mval = buf2hex(edid, edid_len)) == NULL) {
595 				jc->del(jc);
596 				free(conf_name);
597 				if (data_name != NULL)
598 					free(data_name);
599 				return ucmm_resource;
600 			}
601 
602 		/* Else fall back to X11 display name and screen */
603 		} else {
604 			if (display_name == NULL) {
605 				jc->del(jc);
606 				free(conf_name);
607 				if (data_name != NULL)
608 					free(data_name);
609 				return ucmm_no_edid_or_display;
610 			}
611 			mname = "NAME";
612 			if ((mval = strdup(display_name)) == NULL) {
613 				jc->del(jc);
614 				free(conf_name);
615 				if (data_name != NULL)
616 					free(data_name);
617 				return ucmm_resource;
618 			}
619 		}
620 
621 		debug2((errout,"Searching for %s = '%s'\n",mname,mval));
622 		for (ix = 0;;ix++) {
623 			char *key, *pp;
624 			jc_type type;
625 			unsigned char *data;
626 			size_t dataSize;
627 			int ii;
628 
629 			if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok
630 			 || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) {
631 				if (ev == jc_ix_oorange) {
632 					break;
633 				}
634 				debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
635 				free(mval);
636 				jc->del(jc);
637 				free(conf_name);
638 				if (data_name != NULL)
639 					free(data_name);
640 				return ucmm_open_config;
641 			}
642 
643 			if ((pp = jc_get_nth_elem(key, 2)) == NULL) {
644 				continue;
645 			}
646 			if ((ii = atoi(pp)) == 0) {
647 				free(pp);
648 				continue;
649 			}
650 			if (ii > recno)		/* Track biggest, so we know what to create next */
651 				recno = ii;
652 			if ((pp = jc_get_nth_elem(key, 3)) != NULL && strcmp(pp, mname) == 0 && type == jc_string && strcmp(data, mval) == 0) {
653 				/* Found matching record */
654 				free(pp);
655 				break;
656 			}
657 			if (pp != NULL)
658 				free(pp);
659 		}
660 
661 		if (ev == jc_ix_oorange) {
662 			debug2((errout,"No matching display was found\n"));
663 			free(mval);
664 			jc->del(jc);
665 			free(conf_name);
666 			if (data_name != NULL)
667 				free(data_name);
668 			return ucmm_monitor_not_found;
669 			/* (Should we delete the file anyway ???) */
670 		}
671 		free(mval);
672 
673 		debug2((errout,"Deleting record %d key '%s'\n",recno,keyn1));
674 
675 		/* Delete the record */
676 		sprintf(keyn1, "devices/display/%d/", recno);
677 
678 		for (ix = -1;;ix--) {
679 			if ((ev = jc->locate_key(jc, &ix, keyn1, 0, 1)) == jc_ok) {
680 				if ((ev = jc->delete_key(jc, ix, NULL)) != jc_ok) {
681 					debug2((errout,"jcnf delete_key failed with error %d\n",ev));
682 					jc->del(jc);
683 					free(conf_name);
684 					if (data_name != NULL)
685 						free(data_name);
686 					return ucmm_delete_key;
687 				}
688 			} else {
689 				if (ev == jc_ix_oorange) {
690 					break;
691 				}
692 				debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
693 				jc->del(jc);
694 				free(conf_name);
695 				if (data_name != NULL)
696 					free(data_name);
697 				return ucmm_open_config;
698 			}
699 		}
700 
701 		if (data_name != NULL) {
702 			/* See if the profile is used by any other device */
703 
704 			debug2((errout, "Searching for any reference to profile '%s'\n",data_name));
705 			for (ix = 0;;ix++) {
706 				char *key, *pp;
707 				jc_type type;
708 				unsigned char *data;
709 				size_t dataSize;
710 
711 				if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok
712 				 || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) {
713 					if (ev == jc_ix_oorange) {
714 						break;
715 					}
716 					debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
717 					jc->del(jc);
718 					free(conf_name);
719 					if (data_name != NULL)
720 						free(data_name);
721 					return ucmm_access_config;
722 				}
723 				if ((pp = jc_get_nth_elem(key, 3)) == NULL)
724 					continue;
725 				if (strcmp(pp,"ICC_PROFILE") != 0
726 				 || type != jc_string
727 				 || strcmp(data, data_name) != 0) {
728 					free(pp);
729 					continue;
730 				}
731 				free(pp);
732 				break;
733 			}
734 			/* If not, delete the file */
735 			if (ev == jc_ix_oorange) {
736 				debug2((errout,"Deleting profile '%s'\n",data_name));
737 				if (unlink(data_name) != 0) {
738 					debug2((errout,"ucmm unlink '%s' failed\n",data_name));
739 					jc->del(jc);
740 					free(conf_name);
741 					if (data_name != NULL)
742 						free(data_name);
743 					return ucmm_access_config;
744 				}
745 			}
746 		}
747 
748 		/* Update the config */
749 		if ((ev = jc->update(jc)) != 0) {
750 			debug2((errout,"jcnf write to '%s' failed with error %d\n",conf_name,ev));
751 			jc->del(jc);
752 			free(conf_name);
753 			if (data_name != NULL)
754 				free(data_name);
755 			return ucmm_save_config;
756 		}
757 		debug2((errout,"Updated config file '%s'\n",conf_name));
758 
759 		/* We're done with this */
760 		jc->del(jc);
761 		free(conf_name);
762 		if (data_name != NULL)
763 			free(data_name);
764 	}
765 	debug2((errout,"ucmm done profile un-install\n"));
766 	return ucmm_ok;
767 }
768 
769 /* Get an associated monitor profile. */
770 /* Return ucmm_no_profile if there is no installed profile for this */
771 /* monitor. */
772 /* Return an error code */
ucmm_get_monitor_profile(unsigned char * edid,int edid_len,char * display_name,char ** profile)773 ucmm_error ucmm_get_monitor_profile(
774 	unsigned char *edid,	/* Primary device identifier, NULL if none. */
775 	int edid_len,			/* Length of edid data */
776 	char *display_name,	    /* Fall back device association, */
777 	char **profile		    /* Return path to profile. free() afterwards. */
778 ) {
779 	int scope;
780 	char *config_file = "color.jcnf";
781 	char *conf_name = NULL;		/* Configuration path to use */
782 	unsigned int edid_hash = 0;
783 
784 	if (edid != NULL)
785 		edid_hash = fnv_32_buf(edid, edid_len);
786 
787 	debug2((errout,"ucmm_get_monitor_profile called edid 0x%x, disp '%s'\n",edid_hash,display_name));
788 
789 	/* Look at user then local system scope */
790 	for (scope = 0; scope < 2; scope++) {
791 
792 		/* Locate the directories where the config file is, */
793 		{
794 			int npaths;
795 			xdg_error er;
796 			char **paths;
797 			char *tt;
798 
799 			if ((npaths = xdg_bds(&er, &paths, xdg_conf, xdg_read,
800 			                     scope == ucmm_local_system ? xdg_local : xdg_user,
801 			                     config_file)) == 0) {
802 				continue;
803 			}
804 			if ((conf_name = strdup(paths[0])) == NULL) {
805 				xdg_free(paths, npaths);
806 				return ucmm_resource;
807 			}
808 			xdg_free(paths, npaths);
809 		}
810 
811 		/* Get the config file */
812 		{
813 			jc_error ev;
814 			jcnf *jc;
815 			char keyn1[100];
816 			char *mname;			/* Name of key to match to */
817 			char *mval;				/* Value to match */
818 			int ix;
819 			int recno = -1;		/* Number of the last record read */
820 			char *key, *pp;
821 			jc_type type;
822 			unsigned char *data;
823 			size_t dataSize;
824 
825 			/* Open the configuration file for reading */
826 			if ((jc = new_jcnf(&ev, conf_name, jc_read, jc_no_create)) == NULL) {
827 				debug2((errout,"new_jcnf '%s' failed with error %d\n",conf_name,ev));
828 				continue;		/* Try the next scope */
829 			}
830 
831 			/* if EDID supplied, Locate a matching EDID */
832 			if (edid != NULL) {
833 				mname = "EDID";
834 				if ((mval = buf2hex(edid, edid_len)) == NULL) {
835 					debug2((errout,"buf2jex  failed\n"));
836 					jc->del(jc);
837 					free(conf_name);
838 					return ucmm_resource;
839 				}
840 
841 			/* Else fall back to X11 display name and screen */
842 			} else {
843 				if (display_name == NULL) {
844 					debug2((errout,"No EDID and display name  failed\n"));
845 					jc->del(jc);
846 					free(conf_name);
847 					return ucmm_no_edid_or_display;
848 				}
849 				mname = "NAME";
850 				if ((mval = strdup(display_name)) == NULL) {
851 					debug2((errout,"strdup failed\n"));
852 					jc->del(jc);
853 					free(conf_name);
854 					return ucmm_resource;
855 				}
856 			}
857 
858 			debug2((errout,"Searching for %s = '%s'\n",mname,mval));
859 			for (ix = 0;;ix++) {
860 				int ii;
861 
862 				if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok
863 				 || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) {
864 					if (ev == jc_ix_oorange) {
865 						break;
866 					}
867 					debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
868 					free(mval);
869 					jc->del(jc);
870 					free(conf_name);
871 					return ucmm_open_config;
872 				}
873 
874 				if ((pp = jc_get_nth_elem(key, 2)) == NULL) {
875 					continue;
876 				}
877 				if ((ii = atoi(pp)) == 0) {
878 					free(pp);
879 					continue;
880 				}
881 				if (ii > recno)		/* Track biggest, so we know what to create next */
882 					recno = ii;
883 				if ((pp = jc_get_nth_elem(key, 3)) != NULL && strcmp(pp, mname) == 0 && type == jc_string
884 				  && strcmp(data, mval) == 0) {
885 					/* Found matching record */
886 					free(pp);
887 					break;
888 				}
889 				if (pp != NULL)
890 					free(pp);
891 			}
892 
893 			if (ev == jc_ix_oorange) {
894 				debug2((errout,"No matching display was found\n"));
895 				continue;	/* On to the next scope */
896 			}
897 			free(mval);
898 
899 			/* Get the profile path from the  record */
900 			sprintf(keyn1, "devices/display/%d/ICC_PROFILE", recno);
901 			key = keyn1;
902 			debug2((errout,"Looking up record %d key '%s'\n",recno,keyn1));
903 
904 			if ((ev = jc->get_key(jc, -1, &key, &type, &data, &dataSize, NULL)) != jc_ok
905 			 || type != jc_string) {
906 				debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
907 				jc->del(jc);
908 				free(conf_name);
909 				if (ev == jc_ix_oorange) {
910 					continue;			/* try the next config */
911 				}
912 				return ucmm_access_config;
913 			}
914 			if ((*profile = strdup(data)) == NULL) {
915 				debug2((errout,"jcnf get_key malloc failed\n"));
916 				jc->del(jc);
917 				free(conf_name);
918 				return ucmm_resource;
919 			}
920 
921 			/* We're done with this */
922 			jc->del(jc);
923 			free(conf_name);
924 			return ucmm_ok;
925 			debug2((errout,"Returning current profile '%s'\n",data));
926 		}
927 	}
928 	debug2((errout,"Failed to find a current profile\n"));
929 
930 	return ucmm_no_profile;
931 }
932 
933 
934 /* Return an ASCII error message string interpretation of an error number */
ucmm_error_string(ucmm_error erno)935 char *ucmm_error_string(ucmm_error erno) {
936 
937 	switch (erno) {
938 		case ucmm_ok:
939 			return "OK";
940 		case ucmm_resource:
941 			return "Resource failure (e.g. out of memory)";
942 		case ucmm_invalid_profile:
943 			return "Profile is not a valid display ICC profile";
944 		case ucmm_no_profile:
945 			return "There is no associated profile";
946 		case ucmm_no_home:
947 			return "There is no HOME environment variable defined";
948 		case ucmm_no_edid_or_display:
949 			return "There is no edid or display name";
950 		case ucmm_profile_copy:
951 			return "There was an error copying the profile";
952 		case ucmm_open_config:
953 			return "There was an error opening the config file";
954 		case ucmm_access_config:
955 			return "There was an error accessing the config information";
956 		case ucmm_set_config:
957 			return "There was an error setting the config file";
958 		case ucmm_save_config:
959 			return "There was an error saving the config file";
960 		case ucmm_monitor_not_found:
961 			return "The EDID or display wasn't matched";
962 		case ucmm_delete_key:
963 			return "Delete_key failed";
964 		case ucmm_delete_profile:
965 			return "Delete_key failed";
966 	}
967 	return "Unknown error number";
968 }
969 
970 
971 /* ============================================================= */
972 
973 /*
974  * hash_32 - 32 bit Fowler/Noll/Vo hash code
975  *
976  * @(#) $Revision: 1.8 $
977  * @(#) $Id: hash_32.c,v 1.8 2003/10/03 20:38:13 chongo Exp $
978  * @(#) $Source: /usr/local/src/cmd/fnv/RCS/hash_32.c,v $
979  *
980  ***
981  *
982  * Fowler/Noll/Vo hash
983  *
984  * The basis of this hash algorithm was taken from an idea sent
985  * as reviewer comments to the IEEE POSIX P1003.2 committee by:
986  *
987  *	  Phong Vo (http://www.research.att.com/info/kpv/)
988  *	  Glenn Fowler (http://www.research.att.com/~gsf/)
989  *
990  * In a subsequent ballot round:
991  *
992  *	  Landon Curt Noll (http://www.isthe.com/chongo/)
993  *
994  * improved on their algorithm.  Some people tried this hash
995  * and found that it worked rather well.  In an EMail message
996  * to Landon, they named it the ``Fowler/Noll/Vo'' or FNV hash.
997  *
998  * FNV hashes are designed to be fast while maintaining a low
999  * collision rate. The FNV speed allows one to quickly hash lots
1000  * of data while maintaining a reasonable collision rate.  See:
1001  *
1002  *	  http://www.isthe.com/chongo/tech/comp/fnv/index.html
1003  *
1004  * for more details as well as other forms of the FNV hash.
1005  ***
1006  *
1007  * NOTE: The FNV-0 historic hash is not recommended.  One should use
1008  *	 the FNV-1 hash instead.
1009  *
1010  * To use the 32 bit FNV-0 historic hash, pass FNV0_32_INIT as the
1011  * unsigned int hashval argument to fnv_32_buf() or fnv_32_str().
1012  *
1013  * To use the recommended 32 bit FNV-1 hash, pass FNV1_32_INIT as the
1014  * unsigned int hashval argument to fnv_32_buf() or fnv_32_str().
1015  *
1016  ***
1017  *
1018  * Please do not copyright this code.  This code is in the public domain.
1019  *
1020  * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
1021  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
1022  * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR
1023  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
1024  * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1025  * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1026  * PERFORMANCE OF THIS SOFTWARE.
1027  *
1028  * By:
1029  *	chongo <Landon Curt Noll> /\oo/\
1030  *	  http://www.isthe.com/chongo/
1031  *
1032  * Share and Enjoy!	:-)
1033  */
1034 
1035 /*
1036  * 32 bit magic FNV-0 and FNV-1 prime
1037  */
1038 #define FNV_32_PRIME ((unsigned int)0x01000193)
1039 
1040 #define FNV1_32_INIT ((unsigned int)0x811c9dc5)
1041 
1042 /*
1043  * fnv_32_buf - perform a 32 bit Fowler/Noll/Vo hash on a buffer
1044  *
1045  * input:
1046  *	buf	- start of buffer to hash
1047  *	len	- length of buffer in octets
1048  *	hval	- previous hash value or 0 if first call
1049  *
1050  * returns:
1051  *	32 bit hash as a static hash type
1052  *
1053  * NOTE: To use the recommended 32 bit FNV-1 hash, use FNV1_32_INIT as the hval
1054  *	 argument on the first call to either fnv_32_buf() or fnv_32_str().
1055  */
1056 static unsigned int
fnv_32_buf_cont(void * buf,size_t len,unsigned int hval)1057 fnv_32_buf_cont(void *buf, size_t len, unsigned int hval)
1058 {
1059 	unsigned char *bp = (unsigned char *)buf;	/* start of buffer */
1060 	unsigned char *be = bp + len;		/* beyond end of buffer */
1061 
1062 	/*
1063 	 * FNV-1 hash each octet in the buffer
1064 	 */
1065 	while (bp < be) {
1066 
1067 	/* multiply by the 32 bit FNV magic prime mod 2^32 */
1068 #if defined(NO_FNV_GCC_OPTIMIZATION)
1069 	hval *= FNV_32_PRIME;
1070 #else
1071 	hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
1072 #endif
1073 
1074 	/* xor the bottom with the current octet */
1075 	hval ^= (unsigned int)*bp++;
1076 	}
1077 
1078 	/* return our new hash value */
1079 	return hval;
1080 }
1081 
1082 static unsigned int
fnv_32_buf(void * buf,size_t len)1083 fnv_32_buf(void *buf, size_t len) {
1084 	return fnv_32_buf_cont(buf, len, FNV1_32_INIT);
1085 }
1086 
1087